#!/usr/bin/env python
"""
./builder.py component-folder
Part of the CSPBuilder application.

Copyright (c) 2008 Rune M. Friborg, runef@diku.dk.
See LICENSE.txt for licensing details (GPLv2).
"""

import sys # to get cmdline arguments from sys.argv

#Handle commandline arguments
COMPNAME = ''
#VERSION = 'newest'

print sys.argv
if (len(sys.argv) > 1):
    args = sys.argv[1:]
    if ('--help' in args):
        print __doc__
        sys.exit(0)
         
    if len(args) > 0:
        COMPNAME = args[0]
        
    #if '-v' in args:
    #    VERSION = args[args.index('-v')+1]
        

import common

from plugins.builder import *

from extensions import cspxml
from extensions import wizards
from extensions import componentlib
from extensions import insertcomp

# wxWindows specifics
import wx

# Other
import os # For file and directory access
import shutil # For copying
import math
import copy

#----------------------------------------------------------------------------
#                            System Constants
#----------------------------------------------------------------------------

# Our menu item IDs:

menu_DELETE        = 10001
menu_RELOAD        = 10002
menu_TOGGLE_GRID   = 10003
menu_PREFERENCES   = 10004
menu_ABOUT         = 10005 # Help menu items.

menu_INSERT_COMPONENT = 11001
menu_INSERT_CPOINT = 11003
menu_CREATE_CHANNEL = 11004
menu_CANCEL_ACTION = 11005
menu_CREATE_CONNECTION = 11006
menu_CREATE_CONNECTION_TARGET = 11050

menu_REDUCE_CPOINT = 11008

menu_NONE = 11009
menu_EDIT_INSTANCE_NAME = 11010
menu_EDIT_UNCONNECTED_NAME_offset = 13000
menu_EDIT_CONNECTION_TYPE_offset = 14000

# DrawObject type IDs:

obj_CHANNEL          = 1
obj_PROCESS          = 2
obj_CPOINT           = 3
obj_INPUTCONNECTION  = 4
obj_OUTPUTCONNECTION = 5


# Mouse mode IDs

MOUSE_MODE_NORMAL = 0
MOUSE_MODE_CHANNEL = 12001

# Selection handle IDs:

handle_NONE         = 1
handle_TOP_LEFT     = 2
handle_TOP_RIGHT    = 3
handle_BOTTOM_LEFT  = 4
handle_BOTTOM_RIGHT = 5
handle_START_POINT  = 6
handle_END_POINT    = 7

# Dragging operations:

drag_NONE   = 1
drag_RESIZE = 2
drag_MOVE   = 3
drag_DRAG   = 4

# Visual Feedback types:

feedback_LINE    = 1
feedback_RECT    = 2
feedback_LINE_ARROW = 3

# Mouse-event action parameter types:

param_RECT = 1
param_LINE = 2

# Size of the drawing page, in pixels.

PAGE_WIDTH  = 10000
PAGE_HEIGHT = 10000

#----------------------------------------------------------------------------


class DrawingFrame(wx.Frame):
    """ A frame showing the contents of a single document. """

    # ==========================================
    # == Initialisation and Window Management ==
    # ==========================================
    def __init__(self, parent, id, compName, compPath=None, compVersion=''):
        """ Standard constructor.

            'parent', 'id' and 'title' are all passed to the standard wx.Frame
            constructor.  'compName' is the name and path of a saved component to
            load into this frame, if any.
        """
        wx.Frame.__init__(self, parent, id, compName,
                         style = wx.DEFAULT_FRAME_STYLE | wx.WANTS_CHARS |
                                 wx.NO_FULL_REPAINT_ON_RESIZE)

        # Create and load local plugin system
        self.pluginman = PluginManagerBUILDER()
        self.pluginman.load()

        # Execute startup requests
        self.pluginman.executeOnStartup(self)

        # Update path
        sys.path = self.pluginman.updatePath(sys.path)

        # Set global pluginman
        global global_pluginman
        if (global_pluginman == None):
            global_pluginman = self.pluginman
        
        global componentLib
        if (componentLib == None):
            componentLib = componentlib.ComponentLib(global_pluginman)

        # Default package, process name
        self.package_name = 'default'
        self.process_name = 'untitled'
        self.plugin_attr = {}
        
        self.workingDir = os.getcwd()
        self.grid = True

        # Create ProcessLib
        self.processLib = ProcessLib()

        # Setup our menu bar.

        menuBar = wx.MenuBar()

        self.fileMenu = wx.Menu()
        self.fileMenu.Append(wx.ID_NEW,    "New\tCTRL-N")
        self.fileMenu.Append(wx.ID_OPEN,   "Open...\tCTRL-O")
        self.fileMenu.Append(wx.ID_CLOSE,  "Close\tCTRL-W")
        self.fileMenu.AppendSeparator()
        self.fileMenu.Append(wx.ID_SAVE,   "Save\tCTRL-S")
        self.fileMenu.Append(wx.ID_SAVEAS, "Save As...\tCTRL-SHIFT-S")
        self.fileMenu.AppendSeparator()
        submenu = wx.Menu()

        # Add preference items from plugin system
        global_pluginman.prefmenu(self, submenu)

        self.fileMenu.AppendMenu(menu_PREFERENCES, "Preferences", submenu)
        self.fileMenu.AppendSeparator()
        self.fileMenu.Append(wx.ID_EXIT,   "Quit\tCTRL-Q")

        menuBar.Append(self.fileMenu, "File")

        self.editMenu = wx.Menu()
        self.editMenu.Append(menu_DELETE,        "Delete\tDEL")
        self.editMenu.Append(menu_RELOAD,        "Reload")

        menuBar.Append(self.editMenu, "Edit")

        self.viewMenu = wx.Menu()
        self.viewMenu.Append(menu_TOGGLE_GRID, "Toggle Grid")

        menuBar.Append(self.viewMenu, "View")
        
        self.helpMenu = wx.Menu()
        self.helpMenu.Append(menu_ABOUT, "About CSPBuilder")

        menuBar.Append(self.helpMenu, "Help")

        self.SetMenuBar(menuBar)


        # Associate each menu/toolbar item with the method that handles that
        # item.
        menuHandlers = [
        (wx.ID_NEW,    self.doNew),
        (wx.ID_OPEN,   self.doOpen),
        (wx.ID_CLOSE,  self.doClose),
        (wx.ID_SAVE,   self.doSave),
        (wx.ID_SAVEAS, self.doSaveAs),
        (wx.ID_EXIT,   self.doExit),

        (menu_DELETE,        self.doDelete),
        (menu_RELOAD,        self.doForceReload),

        (menu_TOGGLE_GRID, self.doToggleGrid),
        
        (menu_ABOUT, self.doShowAbout)]
        for combo in menuHandlers:
            id, handler = combo
            self.Bind(wx.EVT_MENU, handler, id = id)
                
        
        # Install our own method to handle closing the window.  This allows us
        # to ask the user if he/she wants to save before closing the window, as
        # well as keeping track of which windows are currently open.

        self.Bind(wx.EVT_CLOSE, self.doClose)

        # Install our own method for handling keystrokes.  We use this to let
        # the user move the selected object(s) around using the arrow keys.

        self.Bind(wx.EVT_CHAR_HOOK, self.onKeyEvent)

        # Create splitter
        self.splitter = wx.SplitterWindow(self, -1, style = wx.SP_3DSASH )


        # Setup tool bar area
        self.leftPanel = wx.Panel(self.splitter, -1, style=wx.SIMPLE_BORDER)
        leftSizer = wx.BoxSizer(wx.VERTICAL)

        self.leftPanel.SetAutoLayout(True)
        self.leftPanel.SetSizer(leftSizer)
        
        # Setup our top-most panel.
        self.topPanel = wx.Panel(self.splitter, -1, style=wx.SIMPLE_BORDER)

        # Setup the main drawing area.
        self.drawPanel = wx.ScrolledWindow(self.topPanel, -1,
                                          style=wx.SUNKEN_BORDER)
        self.drawPanel.SetBackgroundColour(wx.WHITE)

        self.drawPanel.EnableScrolling(True, True)
        self.drawPanel.SetScrollbars(20, 20, PAGE_WIDTH / 20, PAGE_HEIGHT / 20)

        self.drawPanel.Bind(wx.EVT_LEFT_DOWN, self.onMouseEvent)
        self.drawPanel.Bind(wx.EVT_LEFT_DCLICK, self.onDoubleClickEvent)
        self.drawPanel.Bind(wx.EVT_RIGHT_DOWN, self.onRightClick)
        self.drawPanel.Bind(wx.EVT_MOTION, self.onMouseEvent)
        self.drawPanel.Bind(wx.EVT_LEFT_UP, self.onMouseEvent)
        self.drawPanel.Bind(wx.EVT_PAINT, self.onPaintEvent)

        #Setup splitter
        self.splitter.SetMinimumPaneSize(5)
        self.splitter.SplitVertically(self.leftPanel, self.topPanel, 140)

        # Position everything in the window.
        topSizer = wx.BoxSizer(wx.HORIZONTAL)
        topSizer.Add(self.drawPanel, 10, wx.EXPAND)

        self.topPanel.SetAutoLayout(True)
        self.topPanel.SetSizer(topSizer)

        self.SetSizeHints(250, 200)
        self.SetSize(wx.Size(800, 600))

        # Refresh counter used to detect a refresh
        self.refreshPanelCounter = 0

        # Mode used in drawing
        self.mouseMode = MOUSE_MODE_NORMAL

        # Channel Mouse mode
        self.selected_start_obj = None
        
        # Setup our frame to hold the contents of a sketch document.

        self.dirty     = False
        self.compName = compName # name of the component without '___csp'
        self.compPath  = compPath # complete path to folder that represents component
        self.key       = '' # key of the component (if uploaded to server) 
        self.contents  = []     # front-to-back ordered list of DrawingObjects.
        self.selection = []     # List of selected DrawingObjects.
        self.undoInfo  = None   # Saved contents for undo.
        self.dragMode  = drag_NONE # Current mouse-drag mode.

        # load contents
        self.editVersion = True
        self.compVersion = 1
        if self.compPath != None:
            # find versions from present folders
            versions = getVersions(compPath)
            versionList = []
            for v in versions['serverVersions']:
                versionList.append(str(v))
            for v in versions['editVersions']:
                versionList.append('e'+str(v))
                
            # show dialog to select version if none is selected
            if compVersion == '':
                if len(versionList) > 1:
                    verDlg = wx.SingleChoiceDialog(self,'Select a version of ' + self.compName + " to open. Versions that are currently\nbeing edited locally are displayed with an 'e' in front of the version no.",'Version',choices=versionList,style= wx.OK)
                    verDlg.ShowModal()
                    selectedVer = verDlg.GetStringSelection()
                    verDlg.Destroy()
                else:
                    selectedVer = versionList[0]
                    
            # pick newest version if this is selected
            elif compVersion == 'newest':
                serverVersions = versions['serverVersions']
                serverVersions.sort()
                selectedVer = serverVersions[-1]
            else:
                selectedVer = compVersion
            
            # do load
            self.compVersion = int(selectedVer.lstrip('e'))
            if not selectedVer.startswith('e'):
                self.editVersion = False
            self.loadContents()
                
            # set new title with version no.
            #self.SetTitle(self.compName + ' (version: ' + selectedVer + ')')
            self.updateTitleVersion(selectedVer)

        self._adjustMenus()

        # Add items to left panel
        self.drawLeftPanel()

        # Finally, set our initial pen, fill and line options.
        self.penColour  = wx.BLACK
        self.fillColour = wx.WHITE
        
    def updateTitleVersion(self, version):
        self.SetTitle(self.compName + ' (version: ' + version + ')')

    def drawLeftPanel(self):
        global componentLib
        sizer = self.leftPanel.GetSizer()
        #sizer.Clear(True)
        
        self.leftBarCtrls = {}
            
        sizer.Add(wx.StaticText(self.leftPanel, -1, "Package:"), 0, wx.ALL, 5)
        
        # get package list
        package_list = componentLib.getPackageList().keys()
        if not (self.package_name in package_list):
            package_list.append(self.package_name)
        package_list.sort()
        print 'package_list:',package_list 
        
        # set package-selector and component name to be read-only if component is on server
        #if self.key:
        #    self.leftBarCtrls['package'] = wx.StaticText(self.leftPanel, -1, self.package_name)
        #    self.leftBarCtrls['component'] = wx.StaticText(self.leftPanel, -1, self.process_name)
        #else:
        self.leftBarCtrls['package'] = wx.ComboBox(self.leftPanel, -1, self.package_name, (100, 50), (130, -1), package_list, wx.CB_DROPDOWN)
        self.leftBarCtrls['component'] = wx.TextCtrl(self.leftPanel, -1, self.process_name, size=(125, -1))
        
        #if self.key:
        #    self.leftBarCtrls['component'].SetWindowStyle(wx.TE_READONLY)
        #    self.leftBarCtrls['component'].Refresh()
            
        sizer.Add(self.leftBarCtrls['package'], 0, wx.LEFT | wx.BOTTOM, 5)
        
        sizer.Add(wx.StaticText(self.leftPanel, -1, "Component:"), 0, wx.ALL, 5)
        
        sizer.Add(self.leftBarCtrls['component'], 0, wx.LEFT | wx.BOTTOM, 5)
        self.Bind(wx.EVT_TEXT, self.onPackageTextCtrlUpdate, self.leftBarCtrls['package'])
        self.Bind(wx.EVT_TEXT, self.onComponentTextCtrlUpdate, self.leftBarCtrls['component'])
        
        # Call plugins
        self.pluginman.drawLeftBar(self)
        
        #self.leftPanel.SetAutoLayout(True)
        #self.leftPanel.SetSizer(sizer)
        sizer.Fit(self.leftPanel)
        #sizer.SetSizeHints(self.leftPanel)



    # ============================
    # == Event Handling Methods ==
    # ============================

    def onPackageTextCtrlUpdate(self, evt):
        self.package_name = self.leftBarCtrls['package'].GetValue()

    def onComponentTextCtrlUpdate(self, evt):
        self.process_name = self.leftBarCtrls['component'].GetValue()


    def onKeyEvent(self, event):
        """ Respond to a keypress event.

            We make the arrow keys move the selected object(s) by one pixel in
            the given direction.
        """
        if event.GetKeyCode() == wx.WXK_UP:
            self._moveObject(0, -20)
        elif event.GetKeyCode() == wx.WXK_DOWN:
            self._moveObject(0, 20)
        elif event.GetKeyCode() == wx.WXK_LEFT:
            self._moveObject(-20, 0)
        elif event.GetKeyCode() == wx.WXK_RIGHT:
            self._moveObject(20, 0)
        elif event.GetKeyCode() == wx.WXK_ESCAPE:
            #Quit connection mouseMode on escape
            self.doCancelAction(None)
        else:
            event.Skip()


    def onMouseEvent(self, event):
        """ Respond to the user clicking on our main drawing panel.

            How we respond depends on the currently selected tool.
        """

        # Draws an arrow when creating a channel
        if (self.mouseMode == MOUSE_MODE_CHANNEL):

            # Erase previous feedback if panel has not been refreshed.
            if self.updatedVisualFeedbackOnRefresh == self.refreshPanelCounter:
                self._drawVisualFeedback(self.selected_start_obj.getPosition(), self.curPt2, feedback_LINE_ARROW, False)
            # Save point and refresh counter for possible erase in next event.
            self.curPt2 = self._getEventCoordinates(event)
            self.updatedVisualFeedbackOnRefresh = self.refreshPanelCounter
            
            # Draw feedback
            self._drawVisualFeedback(self.selected_start_obj.getPosition(), self.curPt2, feedback_LINE_ARROW, False)
        else:
            self.updatedVisualFeedbackOnRefresh = 0



        if not (event.LeftDown() or event.Dragging() or event.LeftUp()):
            return # Ignore mouse movement without click/drag.
        
        feedbackType = feedback_RECT
        action       = self.selectByRectangle
        actionParam  = param_RECT
        selecting    = True
        dashedLine   = True

        if event.LeftDown():
            mousePt = self._getEventCoordinates(event)
            if selecting:
                obj, handle = self._getObjectAndSelectionHandleAt(mousePt)

            if selecting and (obj != None) and (handle != handle_NONE):

                # The user clicked on an object's selection handle.  Let the
                # user resize the clicked-on object.


                if obj.getObjType() == obj_PROCESS:
                    self.dragMode     = drag_RESIZE
                    self.resizeObject = obj

                    self.resizeFeedback = feedback_RECT
                    pos  = obj.getPosition()
                    size = obj.getSize()
                    topLeft  = wx.Point(pos.x, pos.y)
                    topRight = wx.Point(pos.x + size.width, pos.y)
                    botLeft  = wx.Point(pos.x, pos.y + size.height)
                    botRight = wx.Point(pos.x + size.width, pos.y + size.height)

                    if handle == handle_TOP_LEFT:
                        self.resizeAnchor  = botRight
                        self.resizeFloater = topLeft
                    elif handle == handle_TOP_RIGHT:
                        self.resizeAnchor  = botLeft
                        self.resizeFloater = topRight
                    elif handle == handle_BOTTOM_LEFT:
                        self.resizeAnchor  = topRight
                        self.resizeFloater = botLeft
                    elif handle == handle_BOTTOM_RIGHT:
                        self.resizeAnchor  = topLeft
                        self.resizeFloater = botRight

                    self.curPt = mousePt
                    self.resizeOffsetX = self.resizeFloater.x - mousePt.x
                    self.resizeOffsetY = self.resizeFloater.y - mousePt.y
                    endPt = wx.Point(self.curPt.x + self.resizeOffsetX,
                                self.curPt.y + self.resizeOffsetY)
                    self._drawVisualFeedback(self.resizeAnchor, endPt,
                                         self.resizeFeedback, False)

            elif selecting and (self._getObjectAt(mousePt) != None):

                # The user clicked on an object to select it.  If the user
                # drags, he/she will move the object.

                self.select(self._getObjectAt(mousePt))
                self.dragMode = drag_MOVE
                self.moveOrigin = mousePt
                self.curPt      = mousePt
                self._drawObjectOutline(0, 0)

            else:

                # The user is dragging out a selection rect or new object.

                self.dragOrigin = mousePt
                self.curPt      = mousePt
                self.drawPanel.SetCursor(wx.CROSS_CURSOR)
                self.drawPanel.CaptureMouse()
                self._drawVisualFeedback(mousePt, mousePt, feedbackType,
                                         dashedLine)
                self.dragMode = drag_DRAG

            event.Skip()
            return

        if event.Dragging():
            if self.dragMode == drag_RESIZE:

                # We're resizing an object.

                mousePt = self._getEventCoordinates(event)
                if (self.curPt.x != mousePt.x) or (self.curPt.y != mousePt.y):
                    # Erase previous visual feedback.
                    endPt = wx.Point(self.curPt.x + self.resizeOffsetX,
                                    self.curPt.y + self.resizeOffsetY)
                    self._drawVisualFeedback(self.resizeAnchor, endPt,
                                             self.resizeFeedback, False)
                    self.curPt = mousePt
                    # Draw new visual feedback.
                    endPt = wx.Point(self.curPt.x + self.resizeOffsetX,
                                    self.curPt.y + self.resizeOffsetY)
                    self._drawVisualFeedback(self.resizeAnchor, endPt,
                                             self.resizeFeedback, False)

            elif self.dragMode == drag_MOVE:

                # We're moving a selected object.

                mousePt = self._getEventCoordinates(event)
                if (self.curPt.x != mousePt.x) or (self.curPt.y != mousePt.y):
                    # Erase previous visual feedback.
                    self._drawObjectOutline(self.curPt.x - self.moveOrigin.x,
                                            self.curPt.y - self.moveOrigin.y)
                    self.curPt = mousePt
                    # Draw new visual feedback.
                    self._drawObjectOutline(self.curPt.x - self.moveOrigin.x,
                                            self.curPt.y - self.moveOrigin.y)

            elif self.dragMode == drag_DRAG:

                # We're dragging out a new object or selection rect.

                mousePt = self._getEventCoordinates(event)
                if (self.curPt.x != mousePt.x) or (self.curPt.y != mousePt.y):
                    # Erase previous visual feedback.
                    self._drawVisualFeedback(self.dragOrigin, self.curPt,
                                             feedbackType, dashedLine)
                    self.curPt = mousePt
                    # Draw new visual feedback.
                    self._drawVisualFeedback(self.dragOrigin, self.curPt,
                                             feedbackType, dashedLine)

            event.Skip()
            return

        if event.LeftUp():
            if self.dragMode == drag_RESIZE:

                # We're resizing an object.

                mousePt = self._getEventCoordinates(event)
                # Erase last visual feedback.
                endPt = wx.Point(self.curPt.x + self.resizeOffsetX,
                                self.curPt.y + self.resizeOffsetY)
                self._drawVisualFeedback(self.resizeAnchor, endPt,
                                         self.resizeFeedback, False)

                resizePt = wx.Point(mousePt.x + self.resizeOffsetX,
                                   mousePt.y + self.resizeOffsetY)

                if (self.resizeFloater.x != resizePt.x) or \
                   (self.resizeFloater.y != resizePt.y):
                    self._resizeObject(self.resizeObject,
                                       self.resizeAnchor,
                                       self.resizeFloater,
                                       resizePt)
                else:
                    self.drawPanel.Refresh() # Clean up after empty resize.

            elif self.dragMode == drag_MOVE:

                # We're moving a selected object.

                mousePt = self._getEventCoordinates(event)
                
                # Erase last visual feedback.
                self._drawObjectOutline(self.curPt.x - self.moveOrigin.x,
                                        self.curPt.y - self.moveOrigin.y)
                if (self.moveOrigin.x != mousePt.x) or \
                   (self.moveOrigin.y != mousePt.y):
                    newPos = self.snapToGrid(wx.Point(mousePt.x - self.moveOrigin.x,
                                                      mousePt.y - self.moveOrigin.y))
                    self._moveObject(newPos.x, newPos.y)
                else:
                    self.drawPanel.Refresh() # Clean up after empty drag.

            elif self.dragMode == drag_DRAG:

                # We're dragging out a new object or selection rect.

                mousePt = self._getEventCoordinates(event)
                # Erase last visual feedback.
                self._drawVisualFeedback(self.dragOrigin, self.curPt,
                                         feedbackType, dashedLine)
                self.drawPanel.ReleaseMouse()
                self.drawPanel.SetCursor(wx.STANDARD_CURSOR)
                # Perform the appropriate action for the current tool.
                if actionParam == param_RECT:
                    x1 = min(self.dragOrigin.x, self.curPt.x)
                    y1 = min(self.dragOrigin.y, self.curPt.y)
                    x2 = max(self.dragOrigin.x, self.curPt.x)
                    y2 = max(self.dragOrigin.y, self.curPt.y)

                    startX = x1
                    startY = y1
                    width  = x2 - x1
                    height = y2 - y1

                    if not selecting:
                        if ((x2-x1) < 8) or ((y2-y1) < 8): return # Too small.

                    action(x1, y1, x2-x1, y2-y1)
                elif actionParam == param_LINE:
                    action(self.dragOrigin.x, self.dragOrigin.y,
                           self.curPt.x, self.curPt.y)

            self.dragMode = drag_NONE # We've finished with this mouse event.
            event.Skip()


    def onDoubleClickEvent(self, event):
        """ Respond to a double-click within our drawing panel.
        """

        mousePt = self._getEventCoordinates(event)
        obj = self._getObjectAt(mousePt)
        if obj == None:
            self.target = mousePt
            self.doInsertCPoint(event)
            if (self.mouseMode == MOUSE_MODE_CHANNEL) :
                tempobj = self.selection[0]
                self.getFinishConnectionCallback(tempobj.getInput())(event)
                self.getCreateConnectionCallback(tempobj.getOutput())(event)
            return

        if (obj.obj_type == obj_PROCESS):
            if (obj.cspData.has_key('xml')):

                # Add right click menu items from plugin system
                self.pluginman.dcmenu(location_PROCESS_CODE, self, obj)

            if (obj.cspData.has_key('componentFile')):
                global _docList

                myVersion = obj.getVersion()
                print 'opening version', myVersion
                newFrame = DrawingFrame(None, -1, os.path.basename(obj.cspData['componentFile']).rstrip('___csp'),
                                            compPath=obj.cspData['componentFile'], compVersion=myVersion)
                newFrame.Show(True)
                _docList.append(newFrame)



    def onRightClick(self, event):
        """ Respond to the user right-clicking within our drawing panel.

            We select the clicked-on item, if necessary, and display a pop-up
            menu of available options which can be applied to the selected
            item(s).

        """

        mousePt = self._getEventCoordinates(event)
        obj = self._getObjectAt(mousePt)

        if obj == None:

            self.target = mousePt
            
            menu = wx.Menu()
            
            menu.Append(menu_INSERT_COMPONENT, "Insert Component")
            self.Bind(wx.EVT_MENU, self.doInsertComponent, id=menu_INSERT_COMPONENT)

            menu.Append(menu_INSERT_CPOINT, "Insert Connection Point")            
            self.Bind(wx.EVT_MENU, self.doInsertCPoint, id=menu_INSERT_CPOINT)
            
            
            # Add right click menu items from plugin system
            self.pluginman.rcmenu(location_SHEET, self, menu, obj)

        else:
            # Select the clicked-on object.
            
            self.select(obj)
            
            # Build our pop-up menu.
            menu = wx.Menu()
            if obj.getObjType() == obj_PROCESS:
                if (self.mouseMode == MOUSE_MODE_CHANNEL):
                    L = obj.getUnconnectedInputs()

                    if L:
                        submenu = wx.Menu()
                        i = 0
                        for o in L:
                            submenu.Append(menu_CREATE_CONNECTION_TARGET+i, o.target + ' [' + str(o.getType()) + ']')
                            self.Bind(wx.EVT_MENU, self.getFinishConnectionCallback(o), id=menu_CREATE_CONNECTION_TARGET+i)
                            i += 1
                        menu.AppendMenu(menu_CREATE_CONNECTION, "Connect to", submenu)

                    menu.Append(menu_CANCEL_ACTION, "Cancel! Esc")
                    self.Bind(wx.EVT_MENU, self.doCancelAction, id=menu_CANCEL_ACTION)         

                elif (self.mouseMode == MOUSE_MODE_NORMAL):
                    L = obj.getUnconnectedOutputs()

                    if L:
                        submenu = wx.Menu()
                        i = 0
                        for o in L:
                            submenu.Append(menu_CREATE_CONNECTION_TARGET+i, o.target + ' [' + str(o.getType()) + ']')
                            self.Bind(wx.EVT_MENU, self.getCreateConnectionCallback(o), id=menu_CREATE_CONNECTION_TARGET+i)
                            i += 1
                        menu.AppendMenu(menu_CREATE_CONNECTION, "Create connection from", submenu)
                                    
                    menu.AppendSeparator()

                    menu.Append(menu_EDIT_INSTANCE_NAME, 'Edit Instance Name')
                    self.Bind(wx.EVT_MENU, self.doEditInstanceName, id=menu_EDIT_INSTANCE_NAME)

                    offset= menu_EDIT_UNCONNECTED_NAME_offset
                    unconnectedList = obj.getUnconnectedInputs() + obj.getUnconnectedOutputs()
                    for o in unconnectedList:
                        menu.Append(offset, 'Set name of \'' + o.target + '\'')
                        self.Bind(wx.EVT_MENU, self.doEditUnconnectedName, id=offset)
                        offset = offset + 1


                    if (obj.cspData.has_key('xml')):

                        if (unconnectedList):
                            menu.AppendSeparator()

                        connectionList = obj.getInputs() + obj.getOutputs()
                        offset= menu_EDIT_CONNECTION_TYPE_offset
                        for o in connectionList:
                            menu.Append(offset, 'Set type of \'' + o.target + '\'')
                            self.Bind(wx.EVT_MENU, self.doEditConnectionType, id=offset)
                            offset = offset + 1
                    
                        menu.AppendSeparator()                            

                        # Add right click menu items from plugin system
                        self.pluginman.rcmenu(location_PROCESS_CODE, self, menu, obj)
                        

                        
                    menu.AppendSeparator()

                    if (not obj.cspData.has_key('xml')):
                    

                        # Add right click menu items from plugin system
                        self.pluginman.rcmenu(location_PROCESS_LINK, self, menu, obj)
                        
                   
                    # Add right click menu items from plugin system
                    self.pluginman.rcmenu(location_PROCESS, self, menu, obj)

                menu.AppendSeparator()

            if obj.getObjType() == obj_CPOINT:

                # Setup connection menu items
                if (self.mouseMode == MOUSE_MODE_CHANNEL):
                    o = obj.getInput()
                    menu.Append(menu_CREATE_CONNECTION, "Connect to CPoint [" + str(o.getType()) + "]")
                    self.Bind(wx.EVT_MENU, self.getFinishConnectionCallback(o), id=menu_CREATE_CONNECTION)

                    menu.Append(menu_CANCEL_ACTION, "Cancel!")
                    self.Bind(wx.EVT_MENU, self.doCancelAction, id=menu_CANCEL_ACTION)         

                elif (self.mouseMode == MOUSE_MODE_NORMAL):
                    o = obj.getOutput()
                    menu.Append(menu_CREATE_CONNECTION, "Create connection from CPoint [" + str(o.getType()) + "]")
                    self.Bind(wx.EVT_MENU, self.getCreateConnectionCallback(o), id=menu_CREATE_CONNECTION)


                menu.Append(menu_REDUCE_CPOINT, "Reduce")
                self.Bind(wx.EVT_MENU, self.doReduceCPoint, id=menu_REDUCE_CPOINT)

                # Add right click menu items from plugin system
                self.pluginman.rcmenu(location_CPOINT, self, menu, obj)

                menu.AppendSeparator()

            if obj.getObjType() == obj_CHANNEL:
                
                # Add right click menu items from plugin system
                self.pluginman.rcmenu(location_CHANNEL, self, menu, obj)                

            menu.Append(menu_DELETE,    "Delete")
            self.Bind(wx.EVT_MENU, self.doDelete, id=menu_DELETE)

                            
        # Show the pop-up menu.       
        clickPt = self.drawPanel.CalcScrolledPosition(mousePt.x, mousePt.y)
        self.drawPanel.PopupMenu(menu, clickPt)
        menu.Destroy()


    def onPaintEvent(self, event):
        """ Respond to a request to redraw the contents of our drawing panel.
        """

        self.refreshPanelCounter += 1

        dc = wx.PaintDC(self.drawPanel)
        self.drawPanel.PrepareDC(dc)
        dc.BeginDrawing()

        # Clear bg, to make windows redraw the entire draw area
        dc.Clear()

        # Draw grid
        if (self.grid):
            dc.SetPen(wx.Pen((200,200,200,255), 1, wx.SOLID))
            for x in range(PAGE_WIDTH/20):
                dc.DrawLine(x*20, 0, x*20, PAGE_HEIGHT)
           
            for y in range(PAGE_HEIGHT/20):
                dc.DrawLine(0, y*20, PAGE_WIDTH, y*20)
            
        # Draw objects
        for i in range(len(self.contents)-1, -1, -1):
            obj = self.contents[i]
                
            if obj in self.selection:
                obj.draw(dc, True)
            else:
                obj.draw(dc, False)


        dc.EndDrawing()



    # ==========================
    # == Menu Command Methods ==
    # ==========================

    def doNew(self, event):
        """ Respond to the "New" menu command.
        """
        global _docList
        newFrame = DrawingFrame(None, -1, "Untitled")
        newFrame.Show(True)
        _docList.append(newFrame)

    def doOpen(self, event):
        """ Respond to the "Open" menu command.
        """
        global _docList

        curDir = os.getcwd()
        dirDlg = wx.DirDialog(self,'Select a Component-directory to open. A Component-directory ends with "___csp":',curDir)
        if dirDlg.ShowModal() == wx.ID_OK:
            dir = dirDlg.GetPath()
            compPath = os.path.join(os.getcwd(),dir)
            print 'compPath to open:',compPath
            os.chdir(curDir)
            
            # title is basename
            comp = os.path.basename(compPath)
            print 'comp to open:', comp
            if not comp.endswith("___csp"):
                failDlg = wx.MessageDialog(self,"Failed to open as the selected directory wasn't a Component-directory","Open failed",style=wx.OK)
                failDlg.ShowModal()
                failDlg.Destroy()
                dirDlg.Destroy()
                return
            self.compName = comp.rstrip('___csp')
            print self.compName
            
            # find available versions
            versions = getVersions(compPath)
            versionList = []
            for v in versions['serverVersions']:
                versionList.append(str(v))
            for v in versions['editVersions']:
                versionList.append('e'+str(v))
            
            if len(versionList) > 1:
                verDlg = wx.SingleChoiceDialog(self,'Select a version of ' + self.compName + " to open. Versions that are currently\nbeing edited locally are displayed with an 'e' in front of the version no.",'Version',choices=versionList,style= wx.OK)
                verDlg.ShowModal()
                version = verDlg.GetStringSelection()
                verDlg.Destroy()
            else:
                version = versionList[0]
            dirDlg.Destroy()
        else:
            dirDlg.Destroy()
            return

        if (self.compPath == None) and (len(self.contents) == 0):
            self.editVersion = version.startswith('e')
            self.compVersion = int(version.lstrip('e'))
            # Load contents into current (empty) document.
            self.compPath = compPath            
            self.loadContents()
            
            # set values in left panel
            self.leftBarCtrls['package'].SetValue(self.package_name)
            self.leftBarCtrls['component'].SetValue(self.process_name)
            
            print self.compName
            self.SetTitle(self.compName + ' (version: ' + version + ')')
        else:
            # Open a new frame for this document.
            newFrame = DrawingFrame(None, -1, self.compName,compPath=compPath,compVersion=version)
            newFrame.Show(True)
            _docList.append(newFrame)

    def doClose(self, event):
        """ Respond to the "Close" menu command.
        """
        global _docList

        if self.dirty:
            if not self.askIfUserWantsToSave("closing"): return

        _docList.remove(self)
        self.Destroy()

    def doForceReload(self, event):

        # Reload components.
        componentLib.reload()
#        print componentLib.strList()

        realCompName = self.compPath
        self.compPath = 'temp-reload___csp'
        
        self.doForceSave(event)
    
        # Clear
        self.contents  = []
        self.selection = []
        
        # Load content
        self.loadContents(True)

        # Restore filename
        self.compPath = realCompName


    def doForceSave(self, event):
        """ Force saved... used when reloading
        """
        
        if self.compPath == None:
            self.compPath = 'temp___csp'
        #self.doSave(event)
        self.saveContents(False,self.compPath,self.compName,True)

    def doSave(self, event):
        """ Respond to the "Save" menu command.
        """
        if self.compPath != None:
            self.saveContents(False,self.compPath,self.compName)

    def doSaveAs(self, event):
        """ Respond to the "Save As" menu command.
        """
        if self.compPath == None:
            default = ""
        else:
            default = os.path.basename(self.compPath).rstrip('___csp')
        curDir = os.getcwd()
        
        savePath, compName = self.getSaveInfoFromUser(default, curDir)

        if savePath:
            self.saveContents(True,savePath,compName)

    def doExit(self, event):
        """ Respond to the "Quit" menu command.
        """
        global _docList, _app
        for doc in _docList:
            if not doc.dirty: continue
            doc.Raise()
            if not doc.askIfUserWantsToSave("quitting"): return
            _docList.remove(doc)
            doc.Destroy()

        _app.ExitMainLoop()


    def doSelectAll(self, event):
        """ Respond to the "Select All" menu command.
        """
        self.selectAll()

        
        
    def doInsertComponent(self, event):
        """ Respond to the "Insert Component" menu command.
            Ask user to select a component and create component.
        """

        createNewProcess = 'Create new ...'

        selectedComponent = ''
        data = {}
        
        l = self.processLib.getList()
        if (len(l)):
            data['Local Processes'] = l
        
        data.update(componentLib.getPackageList())

        dialog = insertcomp.InsertComponentDialog(self, data, self.pluginman, componentLib)
        result = dialog.ShowModal()
        selectedVersion = 'newest'
        if (result == wx.ID_OK):
            selectedComponent = dialog.GetSelection()
            selectedVersion = dialog.GetVersion().lower()
        elif (result == wx.ID_NEW):
            selectedComponent = createNewProcess

        dialog.Destroy()

        if selectedComponent == createNewProcess:
            # Create new process
            selectedComponent = self.processLib.createProcessWizard(self)
            componentLib.reload()
            
        # After using insert-dialog we have to update comp.lib. because components may have been added from server
        #if dialog.downloadMade:
        #    componentLib.reload()
        
        if (componentLib.componentExists(selectedComponent)):
            # Component
            cObj = componentLib.getComponent(selectedComponent)
            print 'Inserting component'
            self.createComponent(cObj.name, self.target.x, self.target.y, componentObj = cObj, version=selectedVersion)

        elif (self.processLib.processExists(selectedComponent)):
            # Process
            cspData = self.processLib.getProcess(selectedComponent)
            print 'Inserting process'
            self.createComponent(cspData['name'], self.target.x, self.target.y, componentData = cspData, version=selectedVersion)        



    def doInsertCPoint(self, event):
        """ Respond to the "Insert Connection Point" menu command.
        """

        self.createCPoint('', self.target.x, self.target.y)
   


    def doReduceCPoint(self, event):
        """ Reduce to only one connection point
        """

        obj = self.selection[0]

        reduceList = self.findObjList(obj, [obj])

        for neighbour in reduceList:
            if neighbour.obj_type == obj_PROCESS:
                # Search for channels connecting to network in reduceList
                for conn in neighbour.getOutputs():
                    if conn.channel != None:
                        if conn.channel.end.owner.obj_type == obj_CPOINT and (conn.channel.end.owner in reduceList):
                            conn.channel.end = obj.getInput()

                for conn in neighbour.getInputs():
                    if conn.channel != None:
                        if conn.channel.start.owner.obj_type == obj_CPOINT and (conn.channel.start.owner in reduceList):
                            conn.channel.start = obj.getOutput()


        # Delete all other connection points
        self.selection = []
        for x in reduceList:
            if x.obj_type != obj_PROCESS and x != obj:
                self.selection.append(x)
                
        self.doDelete(event)
                
        self.drawPanel.Refresh()
                

    def findObjList(self, obj, objList):
        """ Find neighbours for doCPoint
        """

        if (obj.obj_type == obj_PROCESS):
            # found a leaf
            return objList

        items_in_list = len(objList)
        
        for o in obj.getOutputs():
            if (o.channel != None):
                if not o.channel.end.owner in objList:
                    objList.append(o.channel.end.owner)
        for o in obj.getInputs():
            if (o.channel != None):
                if not o.channel.start.owner in objList:
                    objList.append(o.channel.start.owner)


        if (len(objList)>items_in_list):
            for obj in objList:
                objList = self.findObjList(obj, objList)
        
        return objList



    def doCancelAction(self, event):
        """ Respond to the "Cancel-create channel" menu command.
        """

        if (self.mouseMode == MOUSE_MODE_CHANNEL) :
            self.mouseMode = MOUSE_MODE_NORMAL
            self.selected_start_obj = None

        self.drawPanel.Refresh()

        
    def getCreateConnectionCallback(self, connectionObj):
        """ Used to generate functions that responds to the "Create connection from" menu command.
        """
        frame = self
        def f(event):
            frame.mouseMode = MOUSE_MODE_CHANNEL
            frame.selected_start_obj = connectionObj
            frame.drawPanel.Refresh()

        return f


    def getFinishConnectionCallback(self, connectionObj):
        """ Respond to generate functions that responds to the "connect to" menu command.
        """
        frame = self
        def f(event):
            conn_out = frame.selected_start_obj
            conn_in = connectionObj
                
            frame.createChannel(conn_out, conn_in)

            frame.mouseMode = MOUSE_MODE_NORMAL
            frame.selected_start_obj = None

            frame.drawPanel.Refresh()

        return f


    def doDelete(self, event):
        """ Respond to the "Delete" menu command.
        """

        channels_affected = []
        
        for obj in self.contents:
            if (obj.obj_type == obj_CHANNEL):
                for obj_to_be_deleted in self.selection:
                    if (obj.start.owner == obj_to_be_deleted or obj.end.owner == obj_to_be_deleted):
                        channels_affected.append(obj)

        for chan in channels_affected:
            chan.start.channel = None
            chan.end.channel = None
            if chan in self.contents:
                self.contents.remove(chan)
            del chan

        for obj in self.selection:
            if (obj.obj_type == obj_CHANNEL):
                obj.start.channel = None
                obj.end.channel = None
            
            if obj in self.contents:
                self.contents.remove(obj)
            del obj

        self.deselectAll()
        self.dirty = True

    def doEditInstanceName(self, event):
        """ Edit instance name of process """

        obj = self.selection[0]
        
        dlg = wx.TextEntryDialog(self, 'Edit name:', 'name', obj.name)

        if dlg.ShowModal() == wx.ID_OK:
            obj.name = dlg.GetValue().replace('.','_')

        dlg.Destroy()

        self.doForceReload(event)
        
        self.dirty = True


    def doEditUnconnectedName(self, event):
        """ Edit unconnected channelend names of process """

        obj = self.selection[0]

        lst = obj.getUnconnectedInputs() + obj.getUnconnectedOutputs()
        editItem = lst[event.GetId() - menu_EDIT_UNCONNECTED_NAME_offset]

        dlg = wx.TextEntryDialog(self, 'Edit Unconnected:', 'Name', editItem.name)
            
        if dlg.ShowModal() == wx.ID_OK:
            editItem.name = dlg.GetValue()

        dlg.Destroy()
        self.drawPanel.Refresh()


    def doEditConnectionType(self, event):
        """ Edit unconnected channelend names of process """

        obj = self.selection[0]
        default = ('unknown',(-1))

        lst = obj.getInputs() + obj.getOutputs()
        conn = lst[event.GetId() - menu_EDIT_CONNECTION_TYPE_offset]
        channel_type = conn.type

        if (not self.isValidChannelType(channel_type)):
            channel_type = default
        
        accept = False
        while (not accept):
            dlg = SetConnectionTypeDialog(self, componentLib.getTypes(), str(channel_type))
            if (dlg.ShowModal() == wx.ID_OK):
                try:
                    channel_type = eval(dlg.GetSelection())
                    if (self.isValidChannelType(channel_type)):
                        accept = True
                        
                        # Update connection type
                        conn.type = channel_type
                    else:
                        channel_type = default
                except:
                    channel_type = default
            else:
                accept = True
            dlg.Destroy()
        
        self.drawPanel.Refresh()

    def isValidChannelType(self, channel_type):
        if (type(channel_type) == tuple and len(channel_type) == 2):
            (t, dim) = channel_type
            
            if (type(t) == str):

                if (type(dim) == int and (dim == -1 or dim > 0)):
                    # valid channel type
                    return True


                if (type(dim) == tuple and len(dim) > 0):
                
                    for l in dim:
                        if not (type(l) == int and (l == -1 or l > 0)):
                            # error in length of dim
                            return False

                    # valid channel type
                    return True
            
        # error in channel type structure
        return False


    def isValidChannelConnection(self, channel_type1, channel_type2):
        
        if (not self.isValidChannelType(channel_type1)):
            return (False, 'Type is not valid: '+str(channel_type1))

        if (not self.isValidChannelType(channel_type2)):
            return (False, 'Type is not valid: '+str(channel_type2))

        (t1, dim1) = channel_type1
        (t2, dim2) = channel_type2
        
        if (t1 == 'any' or t2 == 'any'):
            return (True, 'Valid connection')

        if (t1 != t2):
            return (False, 'Types are not matching')

        if (type(dim1) == int and type(dim2) == int):
            if (dim1 == -1 or dim2 == -1):
                return (True, 'Valid connection')

            if (dim1 == dim2):
                return (True, 'Valid connection')

            return (False, 'Wrong length: '+str(dim1)+' != '+str(dim2))
        else: # both are tuples
            if (len(dim1) == len(dim2)):
                for i in range(len(dim1)):
                    if (not (dim1[i] == dim2[i]) and not (dim1[i] == -1 or dim2[i] == -1)):
                        return (False, 'Wrong length of dimension['+str(i)+']: '+str(dim1[i])+' != '+str(dim2[i]))
            else:
                return (False, 'Wrong dimensions: '+str(len(dim1))+' != '+str(len(dim2)))


            return (True, 'Valid connection')
        
        # Should never return from here.
        return (False, 'Unknown error in type matching')
        

    def isValidChannelConnectionGroup(self, channel_obj):

        # Steps:
        # Find all channel_obj
        # Find types in channel group
        # If more than one type, ask user!
        # Set selected type on all cpoints
        
        channel_obj_list = self.findChannelObjList(channel_obj, [channel_obj])

        types = []
        forced = False
        for c in channel_obj_list:
            if (c.forced):
                forced = True
                # Reset forced.
                c.forced = False

            if c.start.owner.obj_type == obj_PROCESS:
                t = c.start.getType()
                if not t in types:
                    types.append(t)

            if c.end.owner.obj_type == obj_PROCESS:
                t = c.end.getType()
                if not t in types:
                    types.append(t)

        # len(types) == 0 : No processes in connection group
        # len(types) == 1 : One type..
        # len(types) > 1  : Conflicting types!

        if (len(types) > 1):
            for i in range(len(types)):
                for j in range(i+1, len(types)):
                    (valid, msg) = self.isValidChannelConnection(types[i], types[j])
                    if not valid:
                        if not forced:
                            dlg = wx.MessageDialog(self, 'Trying to connect  ' + str(types[i]) + '  with  ' + str(types[j]) + '\nForce connection?' , msg, wx.YES_NO | wx.ICON_QUESTION | wx.NO_DEFAULT)
                            if dlg.ShowModal() == wx.ID_YES:
                                forced = True
                            dlg.Destroy()

                        # update forced on all channels in group
                        if forced:
                            for c in channel_obj_list:
                                c.forced = True

                        return forced

        return True


    def findChannelObjList(self, channel_obj, objList):
        """ Find neighbours for 
        """

        # If match, then we have found a leaf
        if (channel_obj.start.owner.obj_type == obj_PROCESS and
            channel_obj.end.owner.obj_type == obj_PROCESS):
            return [channel_obj]

        # Traverse from channel end
        if (channel_obj.end.owner.obj_type == obj_CPOINT):
            if not channel_obj in objList:
                objList.append(channel_obj)

            for o in channel_obj.end.owner.getConnections():
                if (o.channel != None):
                    if not o.channel in objList:
                        objList += self.findChannelObjList(o.channel, objList)
            
        # Traverse from channel start
        if (channel_obj.start.owner.obj_type == obj_CPOINT):
            if not channel_obj in objList:
                objList.append(channel_obj)

            for o in channel_obj.start.owner.getConnections():
                if (o.channel != None):
                    if not o.channel in objList:
                        objList += self.findChannelObjList(o.channel, objList)

        return objList



    def doToggleGrid(self, event):
        """ Toggle grid.
        """

        self.grid = not self.grid

        self.drawPanel.Refresh()


    def doShowAbout(self, event):
        """ Respond to the "About pySketch" menu command.
        """
        dialog = wx.Dialog(self, -1, "About CSPBuilder") # ,
                          #style=wx.DIALOG_MODAL | wx.STAY_ON_TOP)
        dialog.SetBackgroundColour(wx.WHITE)

        panel = wx.Panel(dialog, -1)
        panel.SetBackgroundColour(wx.WHITE)

        panelSizer = wx.BoxSizer(wx.VERTICAL)

        boldFont = wx.Font(panel.GetFont().GetPointSize(),
                          panel.GetFont().GetFamily(),
                          wx.NORMAL, wx.BOLD)


        lab1 = wx.StaticText(panel, -1, "CSPBuilder")
        lab1.SetFont(wx.Font(36, boldFont.GetFamily(), wx.ITALIC, wx.BOLD))
        lab1.SetSize(lab1.GetBestSize())

        imageSizer = wx.BoxSizer(wx.HORIZONTAL)
        imageSizer.Add(lab1, 0, wx.ALL | wx.ALIGN_CENTRE_VERTICAL, 5)

        lab2 = wx.StaticText(panel, -1, "A tool for developing applications " + \
                                       "to solve scientific problems.")
        lab2.SetFont(boldFont)
        lab2.SetSize(lab2.GetBestSize())

        lab3 = wx.StaticText(panel, -1, "The core of the GUI is based on pySketch " + \
                                       "from wxWindows demo folder.")
        lab3.SetFont(boldFont)
        lab3.SetSize(lab3.GetBestSize())

        lab5 = wx.StaticText(panel, -1, "Author: Rune Moellegaard Friborg " + \
                                       "(runef@diku.dk)")
        lab5.SetFont(boldFont)
        lab5.SetSize(lab5.GetBestSize())

        btnOK = wx.Button(panel, wx.ID_OK, "OK")

        panelSizer.Add(imageSizer, 0, wx.ALIGN_CENTRE)
        panelSizer.Add((10, 10)) # Spacer.
        panelSizer.Add(lab2, 0, wx.ALIGN_CENTRE)
        panelSizer.Add((10, 10)) # Spacer.
        panelSizer.Add(lab3, 0, wx.ALIGN_CENTRE)
        panelSizer.Add((10, 10)) # Spacer.
        panelSizer.Add(lab5, 0, wx.ALIGN_CENTRE)
        panelSizer.Add((10, 10)) # Spacer.
        panelSizer.Add(btnOK, 0, wx.ALL | wx.ALIGN_CENTRE, 5)

        panel.SetAutoLayout(True)
        panel.SetSizer(panelSizer)
        panelSizer.Fit(panel)

        topSizer = wx.BoxSizer(wx.HORIZONTAL)
        topSizer.Add(panel, 0, wx.ALL, 10)

        dialog.SetAutoLayout(True)
        dialog.SetSizer(topSizer)
        topSizer.Fit(dialog)

        dialog.Centre()

        btn = dialog.ShowModal()
        dialog.Destroy()


    # =============================
    # == Object Creation Methods ==
    # =============================

    def createChannel(self, start, end, forced = False):

        obj = ChannelObject(start, end, forced)
        self.contents.append(obj)

        if not self.isValidChannelConnectionGroup(obj):
            obj.start.channel = None
            obj.end.channel = None
            
            self.contents.remove(obj)
            del obj
            obj = None

        else:
            self.dirty = True
            self.select(obj)
            
            # Plugin propagate values
            global_pluginman.onCreateConnection(obj.start.owner, obj.end.owner)
            
        return obj
            

    def createComponent(self, name, x, y, componentData = {}, componentObj = None, unConnectedNames = {}, version = 'newest', link=False):
        """ Create a new rectangle object at the given position and size.
        """

        # Hack untill everything is done in objects
        if (componentObj):
            componentData = {
                'name':componentObj.name,
                'process':componentObj.name,
                'package':componentObj.package,
                'plugin_attr':copy.deepcopy(componentObj.plugin_attr),
                'componentFile':componentObj.componentFile,
                'in':componentObj.inputs,
                'out':componentObj.outputs
                }
                
        print 'componentData:',componentData

        newSize = wx.Size(80 + len(componentData['name'])*10, 50 + max( len(componentData['in']), len(componentData['out']))*20 )

            

        obj = ProcessObject(name, position=self.snapToGrid(wx.Point(x,y)),
                            size=newSize ,
                            cspData=componentData,
                            unConnectedNames = unConnectedNames,version=version,link=link)
        self.contents.insert(0, obj)

        self.dirty = True
        self.select(obj)

        return obj


    def createCPoint(self, name, x, y):
        """ Create a new rectangle object at the given position and size.
        """
        obj = CPointObject(name, position=self.snapToGrid(wx.Point(x, y), offset=7),
                            size=wx.Size(14, 14))
        self.contents.insert(0, obj)
        self.dirty = True
        self.select(obj)

        return obj



    # =======================
    # == Selection Methods ==
    # =======================

    def selectAll(self):
        """ Select every DrawingObject in our document.
        """
        self.selection = []
        for obj in self.contents:
            self.selection.append(obj)
        self.drawPanel.Refresh()
        self._adjustMenus()


    def deselectAll(self):
        """ Deselect every DrawingObject in our document.
        """
        self.selection = []
        self.drawPanel.Refresh()
        self._adjustMenus()


    def select(self, obj):
        """ Select the given DrawingObject within our document.
        """
        self.selection = [obj]
        self.drawPanel.Refresh()
        self._adjustMenus()


    def selectMany(self, objs):
        """ Select the given list of DrawingObjects.
        """
        self.selection = objs
        self.drawPanel.Refresh()
        self._adjustMenus()


    def selectByRectangle(self, x, y, width, height):
        """ Select every DrawingObject in the given rectangular region.
        """
        self.selection = []
        for obj in self.contents:
            if obj.objectWithinRect(x, y, width, height):
                self.selection.append(obj)
        self.drawPanel.Refresh()
        self._adjustMenus()


    # ======================
    # == File I/O Methods ==
    # ======================

    def loadContents(self, tempLoad=False):
        """ Load the contents of our document into memory.
        """
        
        if tempLoad:
            xmlFile = os.path.join(self.compPath,'main.csp')
        else:
            if self.editVersion:
                folder = 'e_version%06d' % self.compVersion
            else:
                folder = 'version%06d' % self.compVersion
            xmlFile = os.path.join(self.compPath,folder,'main.csp')
        print 'xmlFile to load:',xmlFile
        #if not os.path.exists(xmlFile):
        #    dlg = wx.MessageDialog(self,'Could not open ' + self.compPath,'Open failed', wx.OK | wx.ICON_INFORMATION)
        #    dlg.ShowModal()
        #    dlg.Destroy()
        #    return

        # find main.csp file
        #if version == "newest":
        #    dir, versionDirs, filelist = os.walk(self.compPath).next()
        #    versionDirs.sort()
        #    xmlFile = os.path.join(dir,versionDirs[-1],'main.csp')
        #else:
        #    folder = 'version%06d' % int(version)
        #    xmlFile = os.path.join(self.compPath,folder,'main.csp')
        #    if not os.path.exists(xmlFile):
        #        dlg = wx.MessageDialog(self,'Given version was not found. Opening newest instead..','Version not found', wx.OK | wx.ICON_INFORMATION)
        #        dlg.ShowModal()
        #        dlg.Destroy()
        #        dir, versionDirs, filelist = os.walk(self.compPath).next()
        #        versionDirs.sort()
        #        xmlFile = os.path.join(dir,versionDirs[-1],'main.csp') 
        
        # load process library for active application
        self.processLib.reload(xmlFile)
        
        # parse the file
        document = cspxml.parseFile(xmlFile)
        for e1 in document.childNodes:
            if (e1.nodeName == 'doc'):
                doc = e1


        # Count main processes in component
        count = 0
        for e1 in doc.childNodes:
            if (e1.nodeName == 'process'):
                count = count + 1
        

        if (count > 1):
            dlg = wx.MessageDialog(self, 'This tool can not handle components with more than one main process', 'Warning!', wx.OK | wx.ICON_INFORMATION)
            dlg.ShowModal()
            dlg.Destroy()

            document.unlink()
            
            self.compPath = None
            self.SetTitle('Untitled')

            return
            

        unConnectedNames = {}
        
        for e1 in doc.childNodes:
            if (e1.nodeName == 'process'):
                
                # Fetch name
                att = cspxml.getAttrList(e1)
                
                if (att.has_key('package')):
                    self.package_name = att['package']
                
                self.process_name = att['name']
                
                self.key = ''
                if att.has_key('key'):
                    self.key = att['key']
                    
                
                # Get plugin values
                self.plugin_attr = global_pluginman.loadAttr(node_PROCESS_CONTAINER, att, self.plugin_attr)

                for e2 in e1.childNodes:
                    # Get name of unconnected process channelends
                    if (e2.nodeName == "channelend"):
                        att = cspxml.getAttrList(e2)
                        unConnectedNames[att['target']] = att['name']

                for e2 in e1.childNodes:
                    # Create connection-point
                    if (e2.nodeName == "cpoint"):
                        att = cspxml.getAttrList(e2)
                        if (att.has_key('name')):
                            
                            if (not att.has_key('x')):
                                att['x'] = 200 # Should pick a random location
                            if (not att.has_key('y')):
                                att['y'] = 200 # Should pick a random location
                                
                            obj = self.createCPoint(att['name'], int(att['x']), int(att['y']))

                            # Get plugin attributes
                            obj.plugin_attr = global_pluginman.loadAttr(node_CPOINT, att, obj.plugin_attr)

                                                
                    # Create code, that can load code, edit it through right click, and save it as well
                    if (e2.nodeName == "process"):
                        att = cspxml.getAttrList(e2)
                        if (self.processLib.processExists2(att['name'])):
                            if (not att.has_key('x')):
                                att['x'] = 100 # Should pick a random location
                            if (not att.has_key('y')):
                                att['y'] = 100 # Should pick a random location
                            if (not att.has_key('w')):
                                att['w'] = 100
                            if (not att.has_key('h')):
                                att['h'] = 100

                            # Creating process with the fetched cspData structure
                            processData = self.processLib.getProcess2(att['name'])
                            version = 'newest'
                            if att.has_key('version'):
                                version = att['version']
                            obj = self.createComponent(att['name'], int(att['x']), int(att['y']), componentData = processData, unConnectedNames = unConnectedNames, version=version)
                                                    
                            # Set size
                            obj.setSize(wx.Size(int(att['w']),int(att['h'])))

                            # Get plugin values
                            obj.plugin_attr = global_pluginman.loadAttr(node_PROCESS, att, obj.plugin_attr)

                    # Create node, for processlink
                    if (e2.nodeName == 'processlink'):
                        att = cspxml.getAttrList(e2)
                        if (not att.has_key('x')):
                            att['x'] = 100 # Should pick a random location
                        if (not att.has_key('y')):
                            att['y'] = 100 # Should pick a random location
                        if (not att.has_key('w')):
                            att['w'] = 100
                        if (not att.has_key('h')):
                            att['h'] = 100

                        if (componentLib.componentExists(att['process'])):

                            # Creating processlink with the fetched cspData structure
                            version = 'newest'
                            cObj = componentLib.getComponent(att['process'])
                            if att.has_key('version'):
                                version = att['version']
                            obj = self.createComponent(att['name'], int(att['x']), int(att['y']), componentObj = cObj, unConnectedNames = unConnectedNames, version=version, link=True)
                            
                            # Set size
                            obj.setSize(wx.Size(int(att['w']),int(att['h'])))

                            # Get plugin attributes
                            obj.plugin_attr = global_pluginman.loadAttr(node_PROCESS_LINK, att, obj.plugin_attr)

                            # Get plugin tags
                            obj.plugin_tags = global_pluginman.loadTags(node_PROCESS_LINK, e2, obj.plugin_tags)


                # Load channels to connect loaded processes
                for e2 in e1.childNodes:
                    if (e2.nodeName == 'channel'):
                        obj_start = None
                        obj_end = None
                        for e3 in e2.childNodes:
                            if (e3.nodeName == 'connection'):
                                att = cspxml.getAttrList(e3)
                                t = att['target']
                                obj_name = t[0:t.index('.')]
                                obj_x = None
                                for obj in self.contents:
                                    if (obj.obj_type == obj_PROCESS or obj.obj_type == obj_CPOINT) and obj_name == obj.name:
                                        obj_x = obj
                                         
                                if obj_x == None:
                                    print "Error: Could not identify process", obj_name
                                    return
                                
                                    
                                obj_conn = t[t.index('.')+1:]
                                if (att['direction'] == 'out'):
                                    # Find offset for connection to process obj_x
                                    if obj_x.obj_type == obj_PROCESS:
                                        obj_start = obj_x.getOutput(obj_conn)
                                    else:
                                        obj_start = obj_x.getOutput()

                                if (att['direction'] == 'in'):
                                    # Find offset for connection to process obj_x
                                    if obj_x.obj_type == obj_PROCESS:
                                        obj_end = obj_x.getInput(obj_conn)
                                    else:
                                        obj_end = obj_x.getInput()

                        if (obj_start != None and obj_end != None):
                            att = cspxml.getAttrList(e2)
                            if att.has_key('forced'):
                                self.createChannel(obj_start, obj_end, forced = bool(att['forced']))
                            else:
                                self.createChannel(obj_start, obj_end)

        document.unlink()

        self.fixDuplicateNames()

        self.dirty = False
        self.selection = []
        self.undoInfo  = None

        #self.drawLeftPanel()
        self.drawPanel.Refresh()
        self._adjustMenus()


    def fixDuplicateNames(self):
        """ Fixes duplicate names of processes. Avoids a conflict in XML format
        """

        fixed = False
        names = []
        for obj in self.contents:
            if (obj.obj_type == obj_PROCESS):
                if (obj.name in names):
                    obj.name = obj.name + '*'
                    fixed = True
                    break
                else:
                    names.append(obj.name)

                
        # Will run recursively until all names are fixed.
        if (fixed):
            self.fixDuplicateNames()

    def doCopyNodes(self, document, destNode, childNodes):
        for item in childNodes:
            if item.nodeName != '#text':
                itemout = document.createElement(item.nodeName)
                att = cspxml.getAttrList(item)
                for a in att:
                    itemout.setAttribute(a, att[a])
                self.doCopyNodes(document, itemout, item.childNodes)
                destNode.appendChild(itemout)

    def saveContents(self, saveAs, savePath, saveName, tempSave=False):
        """ Save the contents of our document to disk.
        """

        #print 'Saving an edit version:', self.editVersion
        
        if not tempSave:
                
            # find new version no.
            newVersion = self.compVersion
            if not saveAs:
                if not self.editVersion:
                    # saving from a version that is not edit-version which means
                    # that we need to increment the version no. according to existing e_version folders
                    editVersions = []
                    versions = getVersions(savePath)
                    if len(versions['editVersions']):
                        editVersions = versions['editVersions'] 
                        #for v in versions['editVersions']:
                        #editVersions.append(v)
                        editVersions.sort()
                        newVersion = editVersions[-1]+1
                    else:
                        newVersion = 1
            else:
                # saveAs
                newVersion = 1
        
        self.fixDuplicateNames()

        document = cspxml.createDom();

        doc = document.firstChild
        process = document.createElement('process')
        process.setAttribute('package', self.package_name)
        process.setAttribute('name', self.process_name)
        if self.key and not saveAs:
            process.setAttribute('key', self.key) # key to component-instance on server
        elif saveAs:
            self.key = ''

        # Insert plugin attributes
        global_pluginman.setAttr(node_PROCESS_CONTAINER, process, self.plugin_attr)

        doc.appendChild(process)

        # Give all connection-points a unique name
        i = 0
        for obj in self.contents:
            if obj.obj_type == obj_CPOINT:
                i += 1
                obj.name = 'cpoint' + str(i)

        unConnectedChannels = []
                
        channels = []
        copyCode = False
        for obj in self.contents:
            if obj.obj_type == obj_CHANNEL:
                outTarget = obj.start.owner.name + '.' + obj.start.target
                inTarget = obj.end.owner.name + '.' + obj.end.target
                
                new = document.createElement('channel')
                new.setAttribute('name', 'chan' + str(i))
                if (obj.forced):
                    new.setAttribute('forced', '1')
                conn_out = document.createElement('connection')
                conn_out.setAttribute('direction', 'out')
                conn_out.setAttribute('target', outTarget)
                new.appendChild(conn_out)

                conn_in = document.createElement('connection')
                conn_in.setAttribute('direction', 'in')
                conn_in.setAttribute('target', inTarget)
                new.appendChild(conn_in)

                process.appendChild(new)
                i += 1

            if obj.obj_type == obj_CPOINT:
                cpoint = document.createElement('cpoint') 
                cpoint.setAttribute('x', str(obj.position.x))
                cpoint.setAttribute('y', str(obj.position.y))
                cpoint.setAttribute('name', obj.name)

                # Insert plugin attributes
                global_pluginman.setAttr(node_CPOINT, cpoint, obj.plugin_attr)

                process.appendChild(cpoint)
                
            if obj.obj_type == obj_PROCESS:

                # Check for unconnected channelends
                for inChannelEnd in obj.getUnconnectedInputs():
                    unConnectedChannels.append(('in',obj.name,inChannelEnd))
                for OutChannelEnd in obj.getUnconnectedOutputs():
                    unConnectedChannels.append(('out',obj.name,OutChannelEnd))
                

                if (obj.cspData.has_key('componentFile')):

                    # Dependant on component file
                    processlink = document.createElement('processlink') 
                    processlink.setAttribute('x', str(obj.position.x))
                    processlink.setAttribute('y', str(obj.position.y))
                    processlink.setAttribute('w', str(obj.size.width))
                    processlink.setAttribute('h', str(obj.size.height))
                    processlink.setAttribute('process', obj.cspData['process'])
                    processlink.setAttribute('name', obj.name)
                    #processlink.setAttribute('file', os.path.basename(obj.cspData['componentFile']))
                    processlink.setAttribute('dir', os.path.basename(obj.cspData['componentFile']))
                    processlink.setAttribute('version', obj.getVersion())

                    # Insert plugin attributes
                    global_pluginman.setAttr(node_PROCESS_LINK, processlink, obj.plugin_attr)

                    # Insert plugin tags
                    global_pluginman.createTags(node_PROCESS_LINK, processlink, obj.plugin_tags)


                    process.appendChild(processlink)

                if (obj.cspData.has_key('xml')):

                    # Copy structure
                    newDom = cspxml.parseString(obj.cspData['xml'])
                    for item in newDom.childNodes:
                        if item.nodeName == 'process':
                            itemout = document.createElement(item.nodeName)
                            att1 = cspxml.getAttrList(item)
                            for a in att1:
                                itemout.setAttribute(a, att1[a])
                                
                            self.doCopyNodes(document, itemout, item.childNodes)
                            
                            # overwrite channelend types
                            for chanend in itemout.childNodes:
                                if chanend.nodeName == 'channelend':
                                    chanend_att = cspxml.getAttrList(chanend)
                                    w= chanend_att['direction']
                                    if (w == 'in'):
                                        chanend.setAttribute('type',  str(obj.getInput(chanend_att['name']).type))
                                    else:
                                        chanend.setAttribute('type',  str(obj.getOutput(chanend_att['name']).type))
                                    
                            itemout.setAttribute('x', str(obj.position.x))
                            itemout.setAttribute('y', str(obj.position.y))
                            itemout.setAttribute('w', str(obj.size.width))
                            itemout.setAttribute('h', str(obj.size.height))
                            
                            # set new version no.
                            if not tempSave:
                                itemout.setAttribute('version','e'+str(newVersion))

                            # Insert plugin attributes
                            global_pluginman.setAttr(node_PROCESS, itemout, obj.plugin_attr)
                            
                            process.appendChild(itemout)
                            
                            # Determine if there is any python code to be copied
                            if itemout.getElementsByTagName('code'):
                                copyCode = True 
                                                    
                    newDom.unlink()

        print 'copyCode:',copyCode

        # Add unconnected channelends
        for channelEnd in unConnectedChannels:
            chEndNode = document.createElement('channelend')
            chEndNode.setAttribute('direction', channelEnd[0])
            chEndNode.setAttribute('name', channelEnd[2].name)
            chEndNode.setAttribute('target', channelEnd[1] + '.' + channelEnd[2].target)
            process.appendChild(chEndNode)
            
        ##### Do the actual saving #####
        
        # create top-folder for component if needed
        if os.path.exists(savePath) and saveAs:
            dlg = wx.MessageDialog(self,'No overwrite functionality',style=wx.OK|wx.ICON_INFORMATION)
            dlg.ShowModal()
            dlg.Destroy()
            return
        
        if not os.path.exists(savePath):
            os.mkdir(savePath)
            
              
        if not tempSave:
            saveFolder = 'e_version%06d' % newVersion
            
            # create path to where file(s) will be saved    
            filepath = savePath + os.sep + saveFolder
            if not os.path.exists(filepath):
                os.mkdir(filepath)
            
            # copy python-file if needed
            if copyCode and not self.editVersion:
                pyPath = os.path.join(self.compPath,'version%06d' % self.compVersion,'main.py')
                if os.path.exists(pyPath):
                    shutil.copy(pyPath, filepath)
            
        else: # temp. save
            filepath = savePath
        
        # save main.csp file
        filepath = filepath + os.sep + 'main.csp'
        print 'filepath:',filepath
        try:
            f = open(filepath, "wb")
            f.write(document.toprettyxml(indent='  ', encoding='utf-8'))
            f.close()
        except:
            dlg = wx.MessageDialog(None, 'If you do not have permissions to save, then use "Save As" to save to your workspace folder.',
                               'Could not save file!!',wx.OK | wx.ICON_INFORMATION)
            dlg.ShowModal()
            dlg.Destroy()

        document.unlink()
        
        self.dirty = False
        if not tempSave:
            self.compPath = savePath
            self.compVersion = newVersion
            self.editVersion = True
            self.compName = saveName
            self.SetTitle(self.compName + ' (version: e' + str(self.compVersion) + ')')
        

    def getSaveInfoFromUser(self,default,curDir):
        compPath = ''
        compName = ''
        #gotInfo = False
        txtDlg = wx.TextEntryDialog(self,"Enter a Component name (a folder with the name 'NAME___csp' will be created to hold the component):",'Component name',default)
        if txtDlg.ShowModal() == wx.ID_OK:
            compName = txtDlg.GetValue()
            dirName = compName + '___csp'
            dirDlg = wx.DirDialog(self,'Choose a parent directory for the Component:',curDir)
            if dirDlg.ShowModal() == wx.ID_OK:
                dir = dirDlg.GetPath()
                compPath = os.path.join(os.getcwd(),dir,dirName)
                
                os.chdir(curDir)
                #print 'title:', title
                print 'compName:', compName
                #print 'fileName:', fileName
                #self.SetTitle(title)
                #self.compPath = compPath
                #gotInfo = True
            dirDlg.Destroy()
        txtDlg.Destroy()
        return compPath, compName

    def askIfUserWantsToSave(self, action):
        """ Give the user the opportunity to save the current document.

            'action' is a string describing the action about to be taken.  If
            the user wants to save the document, it is saved immediately.  If
            the user cancels, we return False.
        """
        if not self.dirty: return True # Nothing to do.

        response = wx.MessageBox("Save changes before " + action + "?",
                                "Confirm", wx.YES_NO | wx.CANCEL, self)

        if response == wx.YES:

            if self.compPath == None:
                
                default = ""

                curDir = os.getcwd()
                #compName = wx.FileSelector("Save File As", 
                #                           default_path=curDir,
                #                           default_filename=default,
                #                           default_extension="csp",
                #                           wildcard="*.csp",
                #                           flags = wx.SAVE | wx.OVERWRITE_PROMPT)
                #if compName == "": return # User cancelled.
                #compName = os.path.join(os.getcwd(), compName)
                #os.chdir(curDir)
                
                #title = os.path.basename(compName)
                #self.SetTitle(title)

                #self.compName = compName
                savePath, compName = self.getSaveInfoFromUser(default, curDir)
                if not savePath:
                    return

                self.saveContents(True,savePath, compName)
            else:
                self.saveContents(False,self.compPath,self.compName)
            return True
        elif response == wx.NO:
            return True # User doesn't want changes saved.
        elif response == wx.CANCEL:
            return False # User cancelled.

    # =====================
    # == Private Methods ==
    # =====================

    def _adjustMenus(self):
        """ Adjust our menus and toolbar to reflect the current state of the
            world.
        """
        canSave   = (self.compPath != None)
        canRevert = (self.compPath != None) and self.dirty
        selection = len(self.selection) > 0
        onlyOne   = len(self.selection) == 1
        front     = onlyOne and (self.selection[0] == self.contents[0])
        back      = onlyOne and (self.selection[0] == self.contents[-1])

        # Enable/disable our menu items.

        self.fileMenu.Enable(wx.ID_SAVE,   canSave)
        self.editMenu.Enable(menu_DELETE,    selection)


    def _resizeObject(self, obj, anchorPt, oldPt, newPt):
        """ Resize the given object.

            'anchorPt' is the unchanging corner of the object, while the
            opposite corner has been resized.  'oldPt' are the current
            coordinates for this corner, while 'newPt' are the new coordinates.
            The object should fit within the given dimensions, though if the
            new point is less than the anchor point the object will need to be
            moved as well as resized, to avoid giving it a negative size.
        """

        self.dirty = True
        
        topLeft  = wx.Point(min(anchorPt.x, newPt.x),
                           min(anchorPt.y, newPt.y))
        botRight = wx.Point(max(anchorPt.x, newPt.x),
                           max(anchorPt.y, newPt.y))

        newWidth  = botRight.x - topLeft.x
        newHeight = botRight.y - topLeft.y

        if obj.getObjType() == obj_CHANNEL:
            # Adjust the line so that its start and end points match the new
            # overall object size.

            startPt = obj.getStartPt()
            endPt   = obj.getEndPt()

            slopesDown = ((startPt.x < endPt.x) and (startPt.y < endPt.y)) or \
                         ((startPt.x > endPt.x) and (startPt.y > endPt.y))

            # Handle the user flipping the line.

            hFlip = ((anchorPt.x < oldPt.x) and (anchorPt.x > newPt.x)) or \
                    ((anchorPt.x > oldPt.x) and (anchorPt.x < newPt.x))
            vFlip = ((anchorPt.y < oldPt.y) and (anchorPt.y > newPt.y)) or \
                    ((anchorPt.y > oldPt.y) and (anchorPt.y < newPt.y))

            if (hFlip and not vFlip) or (vFlip and not hFlip):
                slopesDown = not slopesDown # Line flipped.

            if slopesDown:
                obj.setStartPt(wx.Point(0, 0))
                obj.setEndPt(wx.Point(newWidth, newHeight))
            else:
                obj.setStartPt(wx.Point(0, newHeight))
                obj.setEndPt(wx.Point(newWidth, 0))

        # Finally, adjust the bounds of the object to match the new dimensions.

        obj.setPosition(topLeft)
        obj.setSize(wx.Size(botRight.x - topLeft.x, botRight.y - topLeft.y))
        

        self.drawPanel.Refresh()



    def snapToGrid(self, p, offset=0):
        """ Snap to grid and return new grid point
        """

        return wx.Point((round(p.x / 20)*20)+10 - offset,(round(p.y / 20)*20)+10 - offset)
    

    def _moveObject(self, offsetX, offsetY):
        """ Move the currently selected object(s) by the given offset.
        """
        self.dirty = True

        for obj in self.selection:
            pos = obj.getPosition()
            pos.x = pos.x + offsetX
            pos.y = pos.y + offsetY
            obj.setPosition(pos)

        self.drawPanel.Refresh()



    def _getEventCoordinates(self, event):
        """ Return the coordinates associated with the given mouse event.

            The coordinates have to be adjusted to allow for the current scroll
            position.
        """
        originX, originY = self.drawPanel.GetViewStart()
        unitX, unitY = self.drawPanel.GetScrollPixelsPerUnit()
        return wx.Point(event.GetX() + (originX * unitX),
                       event.GetY() + (originY * unitY))


    def _getObjectAndSelectionHandleAt(self, pt):
        """ Return the object and selection handle at the given point.

            We draw selection handles (small rectangles) around the currently
            selected object(s).  If the given point is within one of the
            selection handle rectangles, we return the associated object and a
            code indicating which selection handle the point is in.  If the
            point isn't within any selection handle at all, we return the tuple
            (None, handle_NONE).
        """
        for obj in self.selection:
            handle = obj.getSelectionHandleContainingPoint(pt.x, pt.y)
            if handle != handle_NONE:
                return obj, handle

        return None, handle_NONE


    def _getObjectAt(self, pt):
        """ Return the first object found which is at the given point.
            Prioritizing processes and connection points
        """

        for obj in self.contents:
            if (not obj.obj_type == obj_CHANNEL) and obj.objectContainsPoint(pt.x, pt.y):
                return obj

        for obj in self.contents:
            if obj.objectContainsPoint(pt.x, pt.y):
                return obj
        return None


    def _drawObjectOutline(self, offsetX, offsetY):
        """ Draw an outline of the currently selected object.

            The selected object's outline is drawn at the object's position
            plus the given offset.

            Note that the outline is drawn by *inverting* the window's
            contents, so calling _drawObjectOutline twice in succession will
            restore the window's contents back to what they were previously.
        """
        if len(self.selection) != 1: return


        position = self.selection[0].getPosition()
        size     = self.selection[0].getSize()

        dc = wx.ClientDC(self.drawPanel)
        self.drawPanel.PrepareDC(dc)
        dc.BeginDrawing()
        dc.SetPen(wx.BLACK_DASHED_PEN)
        dc.SetBrush(wx.TRANSPARENT_BRUSH)
        dc.SetLogicalFunction(wx.INVERT)

        dc.DrawRectangle(position.x + offsetX, position.y + offsetY + 4,
                         size.width, size.height)

        dc.EndDrawing()


    def _drawVisualFeedback(self, startPt, endPt, type, dashedLine):
        """ Draw visual feedback for a drawing operation.

            The visual feedback consists of a line, ellipse, or rectangle based
            around the two given points.  'type' should be one of the following
            predefined feedback type constants:

                feedback_RECT     ->  draw rectangular feedback.
                feedback_LINE     ->  draw line feedback.
                feedback_LINE_ARROW -> draw line with arrow feedback.
                
            if 'dashedLine' is True, the feedback is drawn as a dashed rather
            than a solid line.

            Note that the feedback is drawn by *inverting* the window's
            contents, so calling _drawVisualFeedback twice in succession will
            restore the window's contents back to what they were previously.
        """
        dc = wx.ClientDC(self.drawPanel)
        self.drawPanel.PrepareDC(dc)
        dc.BeginDrawing()
        if dashedLine:
            dc.SetPen(wx.BLACK_DASHED_PEN)
        else:
            dc.SetPen(wx.BLACK_PEN)
        dc.SetBrush(wx.TRANSPARENT_BRUSH)
        dc.SetLogicalFunction(wx.INVERT)

        if type == feedback_RECT:
            dc.DrawRectangle(startPt.x, startPt.y,
                             endPt.x - startPt.x,
                             endPt.y - startPt.y)
        elif type == feedback_LINE:
            dc.DrawLine(startPt.x, startPt.y, endPt.x, endPt.y)
        elif type == feedback_LINE_ARROW:
            dc.DrawLine(startPt.x, startPt.y, endPt.x, endPt.y)

        dc.EndDrawing()


class SetConnectionTypeDialog(wx.Panel):
    def OnSetFocus(self, evt):
        evt.Skip()

    def OnKillFocus(self, evt):
        evt.Skip()

    def __init__(self, parent, types, selection):
        
        self.dialog = wx.Dialog(parent, -1, "Set Type", style = wx.DEFAULT_FRAME_STYLE | wx.WANTS_CHARS)
        wx.Panel.__init__(self, self.dialog, -1)

        self.known_types = []
        for t in types:
            self.known_types.append(str(t))
        
        if not selection in self.known_types:
            self.known_types.append(selection)

        self.selectedType = selection
        self.known_types.sort()


        wx.StaticText(self, -1, "Type:", (10, 15), (75, 18))
        
        # This combobox is created with a preset list of values.
        cb = wx.ComboBox(
            self, 500, self.selectedType, (80, 10),  
            (200, -1), self.known_types, wx.CB_DROPDOWN #|wxTE_PROCESS_ENTER
            )

        self.Bind(wx.EVT_COMBOBOX, self.EvtComboBox, cb)
        self.Bind(wx.EVT_TEXT, self.EvtComboBox, cb)
        self.Bind(wx.EVT_TEXT_ENTER, self.EvtComboBox, cb)

        cb.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
        cb.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
       
        self.btnOK = wx.Button(self, wx.ID_OK, "Select", (300,15), (85,30))


        # Fix: makes windows redraw the dialog
        self.dialog.SetClientSize(wx.Size(400,50))
        
       
    # When the user selects a package, we go here.
    def EvtComboBox(self, evt):
        self.selectedType = evt.GetString()

    def GetSelection(self):
        return self.selectedType

    def ShowModal(self):
        return self.dialog.ShowModal()
        
    def Destroy(self):
        self.dialog.Destroy()

    
#----------------------------------------------------------------------------


class DrawingObject:
    """ An object within the drawing panel.

        A pySketch document consists of a front-to-back ordered list of
        DrawingObjects.  Each DrawingObject has the following properties:

            'obj_type'          What type of object this is (text, line, etc).
            'position'      The position of the object within the document.
            'size'          The size of the object within the document.
            'penColour'     The colour to use for drawing the object's outline.
            'fillColour'    Colour to use for drawing object's interior.
            'textFont'      The text object's font name.
            'textSize'      The text object's point size.
            """

    # ==================
    # == Constructors ==
    # ==================

    def __init__(self, obj_type, position=wx.Point(0, 0), size=wx.Size(0, 0),
                 penColour=wx.BLACK, fillColour=wx.WHITE):
        """ Standard constructor.
        """
        self.obj_type          = obj_type
        self.position          = position
        self.size              = size
        self.penColour         = penColour
        self.fillColour        = fillColour
        self.textFont          = wx.SystemSettings_GetFont(
                                    wx.SYS_DEFAULT_GUI_FONT).GetFaceName()
        self.textSize          = 10

        self.plugin_attr       = {}
        self.plugin_tags       = {}

    # =============================
    # == Object Property Methods ==
    # =============================


    def getObjType(self):
        """ Return this DrawingObject's type.
        """
        return self.obj_type


    def setPosition(self, position):
        """ Set the origin (top-left corner) for this DrawingObject.
        """
        self.position = position


    def getPosition(self):
        """ Return this DrawingObject's position.
        """
        return self.position


    def setPenColour(self, colour):
        """ Set the pen colour used for this DrawingObject.
        """
        self.penColour = colour


    def getPenColour(self):
        """ Return this DrawingObject's pen colour.
        """
        return self.penColour


    def setFillColour(self, colour):
        """ Set the fill colour used for this DrawingObject.
        """
        self.fillColour = colour


    def getFillColour(self):
        """ Return this DrawingObject's fill colour.
        """
        return self.fillColour



    # =======================
    # == Selection Methods ==
    # =======================

    def objectContainsPoint(self, x, y):
        """ Returns True iff this object contains the given point.

            This is used to determine if the user clicked on the object.
        """
        # Firstly, ignore any points outside of the object's bounds.

        extra_size = 2

        if x < (self.position.x - extra_size): return False
        if x > (self.position.x + self.size.x + extra_size*2) : return False
        if y < (self.position.y - extra_size): return False
        if y > (self.position.y + self.size.y + extra_size*2) : return False

        if self.obj_type in [obj_PROCESS, obj_CPOINT]:
            # Rectangles and text are easy -- they're always selected if the
            # point is within their bounds.
            return True

        if self.obj_type in [obj_CHANNEL]:
            return True



    def getSelectionHandleContainingPoint(self, x, y):
        """ Return the selection handle containing the given point, if any.

            We return one of the predefined selection handle ID codes.
        """
        if self.obj_type == obj_CHANNEL:
            # We have selection handles at the start and end points.
            if self._pointInSelRect(x, y, self.position.x + self.startPt.x,
                                          self.position.y + self.startPt.y):
                return handle_START_POINT
            elif self._pointInSelRect(x, y, self.position.x + self.endPt.x,
                                            self.position.y + self.endPt.y):
                return handle_END_POINT
            else:
                return handle_NONE
        else:
            # We have selection handles at all four corners.
            if self._pointInSelRect(x, y, self.position.x, self.position.y):
                return handle_TOP_LEFT
            elif self._pointInSelRect(x, y, self.position.x + self.size.width,
                                            self.position.y):
                return handle_TOP_RIGHT
            elif self._pointInSelRect(x, y, self.position.x,
                                            self.position.y + self.size.height):
                return handle_BOTTOM_LEFT
            elif self._pointInSelRect(x, y, self.position.x + self.size.width,
                                            self.position.y + self.size.height):
                return handle_BOTTOM_RIGHT
            else:
                return handle_NONE



    def objectWithinRect(self, x, y, width, height):
        """ Return True iff this object falls completely within the given rect.
        """
        if x          > self.position.x:                    return False
        if x + width  < self.position.x + self.size.width:  return False
        if y          > self.position.y:                    return False
        if y + height < self.position.y + self.size.height: return False
        return True


    # =====================
    # == Private Methods ==
    # =====================            

    def _drawSelHandle(self, dc, x, y):
        dc.DrawRectangle(x - 3, y - 3, 6, 6)


    def _pointInSelRect(self, x, y, rX, rY):
        """ Return True iff (x, y) is within the selection handle at (rX, ry).
        """
        if   x < rX - 3: return False
        elif x > rX + 3: return False
        elif y < rY - 3: return False
        elif y > rY + 3: return False
        else:            return True

#----------------------------------------------------------------------------

class ConnectionObject(DrawingObject):
    def __init__(self, obj_type, owner, name, target = None, _type = None, offset = wx.Point(0,0)):
        DrawingObject.__init__(self, obj_type)

        self.name = name
        self.target = target

        # process object
        self.owner = owner

        # channel object
        self.channel = None

        self.offset = offset
        self.type = _type


    def setSize(self, size):
        """ Set the size for this DrawingObject.
        """
        self.size = size


    def getSize(self):
        """ Return this DrawingObject's size.
        """
        return self.size


    def getType(self):
        """ Look up type from owner
        """
        if (self.owner.obj_type == obj_CPOINT):
            return self.type

        if (self.owner.obj_type == obj_PROCESS):
            if (self.owner.isComposed()):
                return self.owner.getComposedConnectionType(self.obj_type, self.target)
            else:
                return self.type


    def getPosition(self):
        self.updatePosition()
        return self.position


    def updatePosition(self, offset = None):
        if offset != None:
            self.offset = offset

        pos = self.owner.getPosition();
        self.position = wx.Point(pos.x + self.offset.x, pos.y + self.offset.y)
        

class InputConnectionObject(ConnectionObject):
    def __init__(self, owner, name, target = '', _type = None, offset = wx.Point(0,0)):
        ConnectionObject.__init__(self, obj_INPUTCONNECTION, owner, name, target, _type, offset)

    def draw(self, dc, selected):

        # Test for connections
        if self.channel == None:
            # if unconnected then show connection name
            txt = self.name
            txtWidth = 10 + len(txt)*9
            dc.DrawLine(self.position.x - 20, self.position.y, self.position.x, self.position.y)
            dc.DrawRectangle(self.position.x - ( txtWidth + 20 ), self.position.y - 10, txtWidth, 20)
            dc.DrawText(txt, self.position.x - ( txtWidth + 18 ), self.position.y - 5)


class OutputConnectionObject(ConnectionObject):
    def __init__(self, owner, name, target = '', _type = None, offset = wx.Point(0,0)):
        ConnectionObject.__init__(self, obj_OUTPUTCONNECTION, owner, name, target, _type, offset)

    def draw(self, dc, selected):

        # Test for connections
        if self.channel == None:
            # if unconnected then show connection name
            txt = self.name
            txtWidth = 10 + len(txt)*9
            dc.DrawLine(self.position.x, self.position.y, self.position.x + 20, self.position.y)
            dc.DrawRectangle(self.position.x + 20, self.position.y - 10, txtWidth, 20)
            dc.DrawText(txt, self.position.x + 22, self.position.y - 5)


class CPointObject(DrawingObject):
    def __init__(self, name, position=wx.Point(0,0), size=wx.Size(0,0)):
        DrawingObject.__init__(self, obj_CPOINT, position, size)
        
        self.input = []
        self.output = []
        self.name = name


    def setSize(self, size):
        """ Set the size for this DrawingObject.
        """
        self.size = size


    def getSize(self):
        """ Return this DrawingObject's size.
        """
        return self.size

    def getConnections(self):
        L = []
        for o in self.input + self.output:
            if o.channel:
                L.append(o)
        return L        

    def getInputs(self):
        L = []
        for o in self.input:
            if o.channel:
                L.append(o)
        return L

    def getOutputs(self):
        L = []
        for o in self.output:
            if o.channel:
                L.append(o)
        return L
        

    def getInput(self):
        o = InputConnectionObject(self,'',offset = wx.Point(7,7))
        self.input.append(o)
        return o

    def getOutput(self):
        o = OutputConnectionObject(self,'',offset = wx.Point(7,7))
        self.output.append(o)
        return o

    def draw(self, dc, selected):
        """ Private routine to draw this DrawingObject.
        """
        position = self.position

        # Draw plugins
        global_pluginman.draw(location_CPOINT, 1, dc, self, position, selected)

        dc.SetBrush(wx.BLACK_BRUSH)
        dc.SetPen(wx.TRANSPARENT_PEN)

        # Draw plugins
        global_pluginman.draw(location_CPOINT, 2, dc, self, position, selected)

        r = self.size.width / 2
        dc.DrawCircle(position.x + r, position.y + r, r)

        # Draw plugins
        global_pluginman.draw(location_CPOINT, 3, dc, self, position, selected)

        if selected:
            # Draw selection handles at all four corners.
            self._drawSelHandle(dc, position.x, position.y)
            self._drawSelHandle(dc, position.x + self.size.width,
                                position.y)
            self._drawSelHandle(dc, position.x,
                                position.y + self.size.height)
            self._drawSelHandle(dc, position.x + self.size.width,
                                position.y + self.size.height)
        

class ChannelObject(DrawingObject):
    def __init__(self, start, end, forced = False):
        DrawingObject.__init__(self, obj_CHANNEL)

        self.arrow             = (wx.Point(0,0),0,0)

        self.start = start
        self.start.channel = self

        self.end = end
        self.end.channel = self

        self.startPt           = wx.Point(0,0)
        self.endPt             = wx.Point(0,0)

        self.forced = forced

    def setSize(self, size):
        """ Set the size for this DrawingObject.
        """
        self.size = size


    def getSize(self):
        """ Return this DrawingObject's size.
        """
        return self.size


    def setStartPt(self, startPt):
        """ Set the starting point for this line DrawingObject.
        """
        self.startPt = startPt


    def getStartPt(self):
        """ Return the starting point for this line DrawingObject.
        """
        return self.startPt


    def setEndPt(self, endPt):
        """ Set the ending point for this line DrawingObject.
        """
        self.endPt = endPt


    def getEndPt(self):
        """ Return the ending point for this line DrawingObject.
        """
        return self.endPt


    def setArrow(self, point, angle, length):
        """ Only used for lines
        """
        
        self.arrow = (point, angle, length)

    def getArrow(self):
        """ Return arrow position and angle
        """

        return self.arrow

    def draw(self, dc, selected):

        """ Private routine to draw this DrawingObject.
        """

        pt1= self.start.getPosition()
        pt2= self.end.getPosition()

        # Find top left corner
        topLeftX = min(pt1.x, pt2.x)
        topLeftY = min(pt1.y, pt2.y)

        # Set start and end point
        new_p1 = wx.Point((pt1.x - topLeftX), (pt1.y - topLeftY))
        new_p2 = wx.Point((pt2.x - topLeftX), (pt2.y - topLeftY))

        self.setStartPt(new_p1)
        self.setEndPt(new_p2)
        self.setPosition(wx.Point(topLeftX, topLeftY))
        self.setSize(wx.Size(abs(new_p2.x - new_p1.x), abs(new_p2.y - new_p1.y)))

        diff_x = pt2.x - pt1.x
        diff_y = pt2.y - pt1.y
        length = math.sqrt(diff_x*diff_x+diff_y*diff_y)
        if (length > 0):
            angle = math.acos(diff_x/length)/(math.pi/180)
        else:
            angle = 0

        if (diff_y > 0):
            self.setArrow(
                wx.Point(topLeftX + abs(new_p2.x - new_p1.x)/2, topLeftY + abs(new_p2.y - new_p1.y)/2),
                angle ,
                length
            )
        else:
            self.setArrow(
                wx.Point(topLeftX + abs(new_p2.x - new_p1.x)/2, topLeftY + abs(new_p2.y - new_p1.y)/2),
                360 - angle,
                length
            )


        position = self.position
        dc.SetBrush(wx.BLACK_BRUSH)
            
        if (selected):
            dc.SetPen(wx.Pen(wx.BLACK, 2, wx.SOLID))
        else:
            dc.SetPen(wx.BLACK_PEN)

        dc.DrawLine(position.x + self.startPt.x,
            position.y + self.startPt.y,
            position.x + self.endPt.x,
            position.y + self.endPt.y)

        dc.SetPen(wx.BLACK_PEN)

        (arrow_position, arrow_angle, arrow_length) = self.getArrow()
        if (arrow_length > 20):
            dc.DrawLine(arrow_position.x - 5*math.cos(math.pi*((arrow_angle+90)/180.0)),
                        arrow_position.y - 5*math.sin(math.pi*((arrow_angle+90)/180.0)),
                        arrow_position.x + 5*math.cos(math.pi*((arrow_angle)/180.0)),
                        arrow_position.y + 5*math.sin(math.pi*((arrow_angle)/180.0)))

            dc.DrawLine(arrow_position.x - 5*math.cos(math.pi*((arrow_angle-90)/180.0)),
                        arrow_position.y - 5*math.sin(math.pi*((arrow_angle-90)/180.0)),
                        arrow_position.x + 5*math.cos(math.pi*((arrow_angle)/180.0)),
                        arrow_position.y + 5*math.sin(math.pi*((arrow_angle)/180.0)))


        dc.DrawCircle(position.x + self.startPt.x, position.y + self.startPt.y, 2)
        dc.DrawCircle(position.x + self.endPt.x, position.y + self.endPt.y, 2)



class ProcessObject(DrawingObject):
    def __init__(self, name, position=wx.Point(0,0), size=wx.Size(0,0), cspData= {}, unConnectedNames = {}, version = 'newest', link = False):
        DrawingObject.__init__(self, obj_PROCESS,
                               position=position,
                               size=size)
        
        # Inherit plugin attributes
        if (cspData.has_key('plugin_attr')):
            self.plugin_attr = cspData['plugin_attr']
            del cspData['plugin_attr']
        if (cspData.has_key('plugin_tags')):
            self.plugin_tags = cspData['plugin_tags']
            del cspData['plugin_tags']

        self.input = []
        self.output = []
        
        self.version = version
        print 'New procObj with version:',version

        self.name = name
        self.cspData           = cspData
        self.link = link

        # extract input and output information
        x_offset = 0
        y_offset = 40
        for (target,t) in self.cspData['in']:
            name = target
            key = self.name + '.' + target
            if (unConnectedNames.has_key(key)):
                name = unConnectedNames[key]
            self.input.append(InputConnectionObject(self, name, target = target, _type = t, offset=wx.Point(x_offset, y_offset)))
            y_offset += 20

        x_offset = size.x
        y_offset = 40
        for (target,t) in self.cspData['out']:
            name = target
            key = self.name + '.' + target
            if (unConnectedNames.has_key(key)):
                name = unConnectedNames[key]
            self.output.append(OutputConnectionObject(self, name, target = target, _type = t, offset=wx.Point(x_offset, y_offset)))
            y_offset += 20

        # Cleanup
        del self.cspData['in']
        del self.cspData['out']

    def isLink(self):
        return self.link

    def isComposed(self):
        return self.cspData.has_key('componentFile')

    def getComposedConnectionType(self, obj_type, target):
        
        cObj = componentLib.getComponent(self.cspData['process'])
        
        if (obj_type == obj_INPUTCONNECTION):
            for x in cObj.inputs:
                if target == x[0]:
                    return x[1]

        if (obj_type == obj_OUTPUTCONNECTION):
            for x in cObj.outputs:
                if target == x[0]:
                    return x[1]
        
        return ('unknown', -1)
    
    def getAvailVersions(self):
        
        versions = {'serverVersions': [], 'editVersions': []}
        for dirName in os.listdir(self.cspData['componentFile']):
            if dirName.startswith('version'):
                versions['serverVersions'].append(int(dirName[-6:]))
            elif dirName.startswith('e_version'):
                versions['editVersions'].append(int(dirName[-6:]))
        return versions
        
        #versions = []
        #for dirName in os.listdir(self.cspData['componentFile']):
        #    if dirName.startswith('version'):
        #        versions.append(str(int(dirName[-6:])))
        #    elif dirName.startswith('e_version'):
        #        versions.append('e' + str(int(dirName[-6:])))
        #return versions

    def getVersion(self):
        return self.version

    def setVersion(self, version):
        self.version = version

    def setSize(self, size):
        """ Set the size for this DrawingObject.
        """
        self.size = size
        
        # update Input and Output offsets
        x_offset = 0
        y_offset = 40
        for o in self.getInputs():
            o.updatePosition(wx.Point(x_offset, y_offset))
            y_offset += 20

        x_offset = size.x
        y_offset = 40
        for o in self.getOutputs():
            o.updatePosition(wx.Point(x_offset, y_offset))
            y_offset += 20
        
        
    def getSize(self):
        """ Return this DrawingObject's size.
        """
        return self.size


    def getInput(self, t):
        for o in self.input:
            if o.target == t:
                return o

    def getInputs(self):
        return self.input
        
    def getUnconnectedInputs(self):
        L = []
        for o in self.input:
            if (o.channel == None):
                L.append(o)
        return L

    def getOutput(self, t):
        for o in self.output:
            if o.target == t:
                return o

    def getOutputs(self):
        return self.output

    def getUnconnectedOutputs(self):
        L = []
        for o in self.output:
            if (o.channel == None):
                L.append(o)
        return L

            
    def draw(self, dc, selected):
        """ Private routine to draw this DrawingObject.
        """

        dc.SetPen(wx.Pen(self.penColour, 1, wx.SOLID))
        dc.SetBrush(wx.Brush(self.fillColour, wx.SOLID))

        position = self.position

        #Set font
        font = wx.Font(self.textSize, wx.DEFAULT, wx.NORMAL, wx.NORMAL,
                      False, self.textFont)
        dc.SetFont(font)


        # Draw plugins
        global_pluginman.draw(location_PROCESS, 1, dc, self, position, selected)

        # Draw frame
        dc.DrawRoundedRectangle(position.x, position.y,
                         self.size.width, self.size.height, 8)

        # Set correct color
        dc.SetPen(wx.BLACK_PEN)

        # Draw plugins
        global_pluginman.draw(location_PROCESS, 2, dc, self, position, selected)


        # Set title
        dc.DrawText(self.name, position.x + 5, position.y + 5)            

        # Create connections
        for c in self.getInputs():
            pos = c.getPosition()
            dc.DrawText(c.target, pos.x + 5, pos.y - 5)

        for c in self.getOutputs():
            pos = c.getPosition()
            textwidth, textheight = dc.GetTextExtent(c.target)
            dc.DrawText(c.target, pos.x - (textwidth + 5), pos.y - 5)

        for c in self.getInputs():
            c.draw(dc, selected)
        for c in self.getOutputs():
            c.draw(dc, selected)


        # Draw plugins
        global_pluginman.draw(location_PROCESS, 3, dc, self, position, selected)



        if selected:
            dc.SetPen(wx.TRANSPARENT_PEN)
            dc.SetBrush(wx.BLACK_BRUSH)

            # Draw selection handles at all four corners.
            self._drawSelHandle(dc, position.x, position.y)
            self._drawSelHandle(dc, position.x + self.size.width,
                                    position.y)
            self._drawSelHandle(dc, position.x,
                                    position.y + self.size.height)
            self._drawSelHandle(dc, position.x + self.size.width,
                                    position.y + self.size.height)


#----------------------------------------------------------------------------

class ProcessLib:
    """ A class for loading and administrating the processes in the active application
    """

    def __init__(self):
        """ Standard constructor.
        """

        self.libData = {}

    def reload(self, filename):
        self.libData = {}
        self.load(filename)
        print 'processLib:',self.libData

    def load(self, filename):
        
        if (filename[-4:] == '.csp'):
            name = filename[0:-4]
            # Parse file and main process, and extract processes.
            document = cspxml.parseFile(filename)
            for e1 in document.childNodes:
                if (e1.nodeName == "doc"):
                    for e1x in e1.childNodes:
                        if (e1x.nodeName == "process"):
                            for e2 in e1x.childNodes:
                                if (e2.nodeName == "process"):
                                    self.createProcessStructure(e2)

            document.unlink()
                    

    def strList(self):
        L = []
        for x in self.libData:
            L.append(x)
        return L

    def getList(self):
        return self.libData


    def replaceLineWithStringList(self, line, id, stringList):
       result = ''
       if (line.find(id) != -1):
          for x in stringList:
             result = result + line.replace(id, x)
       return result


    def createProcessWizard(self, frame):

        global componentLib
        data = wizards.runCreateProcessWizard(frame, global_pluginman, componentLib)

        if (data):

            # Create process XML
            document = cspxml.parseString("<doc><process package='"+data['packageName']+"' name='"+data['componentName']+"'><process version='e1' name='"+data['componentName']+"'></process></process></doc>");

            doc = document.firstChild
            process1 = doc.firstChild
            process2 = process1.firstChild

            for i in range(data['inChannels']):
                channelend = document.createElement('channelend')
                channelend.setAttribute('direction', 'in')
                channelend.setAttribute('name', 'in'+str(i))
                channelend.setAttribute('target', data['componentName'] + 'Func' + '.' + 'in'+str(i))
                channelend.setAttribute('type', 'unknown')
                process2.appendChild(channelend)
                 
                channelend = document.createElement('channelend')
                channelend.setAttribute('direction', 'in')
                channelend.setAttribute('name', 'in'+str(i))
                channelend.setAttribute('target', data['componentName'] + '.' + 'in'+str(i))
                process1.appendChild(channelend)
                
            for i in range(data['outChannels']):
                channelend = document.createElement('channelend')
                channelend.setAttribute('direction', 'out')
                channelend.setAttribute('name', 'out'+str(i))
                channelend.setAttribute('target', data['componentName'] + 'Func' + '.' + 'out'+str(i))
                channelend.setAttribute('type', 'unknown')
                process2.appendChild(channelend)

                channelend = document.createElement('channelend')
                channelend.setAttribute('direction', 'out')
                channelend.setAttribute('name', 'out'+str(i))
                channelend.setAttribute('target', data['componentName'] + '.' + 'out'+str(i))
                process1.appendChild(channelend)

            code = document.createElement('code')
            code.setAttribute('func', data['componentName'] + 'Func')
            
            #code.setAttribute('src', data['pythonFilename'])
            code.setAttribute('src', 'main')

              
            if (data['configurable']):
                code.setAttribute('configurable', 'yes')

            process2.appendChild(code)
            
            compDirPath = data['componentPath'] + os.sep + data['componentName'] + '___csp'
            verDirPath = compDirPath + os.sep + 'e_version000001'
            filePath = verDirPath + os.sep + 'main.csp'
            codeFilePath = verDirPath + os.sep + 'main.py'
            
            os.mkdir(compDirPath)
            os.mkdir(verDirPath)
            f = open(filePath, "wb")
            f.write(doc.toprettyxml(indent='  ', encoding='utf-8'))
            f.close()
            document.unlink()
            
            if not os.path.exists(codeFilePath):
                f = open(codeFilePath, "wb")
                f.write(data['code'])
                f.close()
            else:
                print codeFilePath + ' already exists!!!'

            # Save component
            #componentPathAndFilename = data['componentPath'] + '/' + data['componentFilename'] + '.csp'
            #f = open(componentPathAndFilename, "wb")
            #f.write(doc.toprettyxml(indent='  ', encoding='utf-8'))
            #f.close()
            #document.unlink()

            # New component name
            newCName = data['componentName']
              

            # Create code file.
            #codePathAndFilename = data['componentPath'] + '/' + data['pythonFilename'] + '.py'
            #if not os.path.exists(codePathAndFilename):
            #    f = open(codePathAndFilename, "wb")
            #    f.write(data['code'])
            #    f.close()
            #else:
            #    print codePathAndFilename + ' already exists!!!'

        else:
            return False
        
        return newCName

    def createProcessStructure(self, processNode):

        # Name
        att= cspxml.getAttrList(processNode)
        Cname = str('_' + att['name'])
        data = att
                                    
        # Save process name, in case we change the general name later
        data['process'] = att['name']

        # Channels
        data['in'] = []
        data['out'] = []
        for elem in processNode.childNodes:
            if (elem.nodeName == 'channelend'):
                att = cspxml.getAttrList(elem)
                t = ''
                try:
                    t = eval(att['type'])
                except:
                    pass
                data[att['direction']].append((att['name'], t))

        data['xml'] = processNode.toxml(encoding='utf-8')

        # Connections
        data['connections'] = []

        # Adding to libData
        if (Cname != ''):
            self.libData[Cname] = data

        return Cname

    
    def processExists(self, processStr):
        if (processStr in self.libData):
            return True
        else:
            return False
        

    def getProcess(self, processStr):
        if (processStr in self.libData):
            return self.libData[processStr].copy();
        else:
            return {}

    def processExists2(self, processName):
        for process in self.libData:
            if (self.libData[process]['process'] == processName):
                return True

        return False
        

    def getProcess2(self, processName):
        for process in self.libData:
            if (self.libData[process]['process'] == processName):
                return self.libData[process].copy();

        return {}


#----------------------------------------------------------------------------

class ExceptionHandler:
    """ A simple error-handling class to write exceptions to a text file.

        Under MS Windows, the standard DOS console window doesn't scroll and
        closes as soon as the application exits, making it hard to find and
        view Python exceptions.  This utility class allows you to handle Python
        exceptions in a more friendly manner.
    """

    def __init__(self):
        """ Standard constructor.
        """
        self._buff = ""
        if os.path.exists("errors.txt"):
            os.remove("errors.txt") # Delete previous error log, if any.


    def write(self, s):
        """ Write the given error message to a text file.

            Note that if the error message doesn't end in a carriage return, we
            have to buffer up the inputs until a carriage return is received.
        """
        if (s[-1] != "\n") and (s[-1] != "\r"):
            self._buff = self._buff + s
            return

        try:
            s = self._buff + s
            self._buff = ""

            if s[:9] == "Traceback":
                # Tell the user than an exception occurred.
                wx.MessageBox("An internal error has occurred.\nPlease " + \
                             "refer to the 'errors.txt' file for details.",
                             "Error", wx.OK | wx.CENTRE | wx.ICON_EXCLAMATION)

            f = open("errors.txt", "a")
            f.write(s)
            f.close()
        except:
            pass # Don't recursively crash on errors.

#----------------------------------------------------------------------------

class CSPApp(wx.App):
    """ The main application object.
    """
    def OnInit(self):
        """ Initialise the application.
        """
        global _docList
        _docList = []

        if COMPNAME == '':
            # No file name was specified on the command line -> start with a
            # blank document.
            frame = DrawingFrame(None, -1, "Untitled")
            frame.Centre()
            frame.Show(True)
            _docList.append(frame)
        else:
            # Load the file(s) specified on the command line
            compPath = os.path.join(os.getcwd(), COMPNAME)
            
            if os.path.isdir(compPath):
                frame = DrawingFrame(None, -1,
                                    os.path.basename(compPath).rstrip('___csp'),
                                    compPath)
                frame.Show(True)
                _docList.append(frame)

        return True

#----------------------------------------------------------------------------

def getVersions(path):
    versions = {'serverVersions': [], 'editVersions': []}
    dirs = os.listdir(path)
    for v in dirs:
        if v.startswith('version'):
            versions['serverVersions'].append(int(v[-6:]))
        elif v.startswith('e_version'):
            versions['editVersions'].append(int(v[-6:]))
    return versions

    #versions = {'serverVersions': [], 'editVersions': []}
    #dirs = os.listdir(path)
    #for v in dirs:
    #    if v.startswith('version'):
    #        versions['serverVersions'].append(str(int(v[-6:])))
    #    elif v.startswith('e_version'):
    #        versions['editVersions'].append(str(int(v[-6:])))
    #return versions

def main():
    """ Start up the application.
    """
    global _app
    global global_pluginman
    global_pluginman = None
 
    global componentLib
    componentLib = None


    # Redirect python exceptions to a log file.
    sys.stderr = ExceptionHandler()

    # Create and start the CSPBuilder application.
    _app = CSPApp(0)
    _app.MainLoop()


if __name__ == "__main__":
    main()
